Edit on GitHub

sqlglot.generator

   1from __future__ import annotations
   2
   3import logging
   4import re
   5import typing as t
   6from collections import defaultdict
   7from functools import reduce, wraps
   8
   9from sqlglot import exp
  10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages
  11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get
  12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS
  13from sqlglot.time import format_time
  14from sqlglot.tokens import TokenType
  15
  16if t.TYPE_CHECKING:
  17    from sqlglot._typing import E
  18    from sqlglot.dialects.dialect import DialectType
  19
  20    G = t.TypeVar("G", bound="Generator")
  21    GeneratorMethod = t.Callable[[G, E], str]
  22
  23logger = logging.getLogger("sqlglot")
  24
  25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)")
  26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
  27
  28
  29def unsupported_args(
  30    *args: t.Union[str, t.Tuple[str, str]],
  31) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
  32    """
  33    Decorator that can be used to mark certain args of an `Expression` subclass as unsupported.
  34    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
  35    """
  36    diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {}
  37    for arg in args:
  38        if isinstance(arg, str):
  39            diagnostic_by_arg[arg] = None
  40        else:
  41            diagnostic_by_arg[arg[0]] = arg[1]
  42
  43    def decorator(func: GeneratorMethod) -> GeneratorMethod:
  44        @wraps(func)
  45        def _func(generator: G, expression: E) -> str:
  46            expression_name = expression.__class__.__name__
  47            dialect_name = generator.dialect.__class__.__name__
  48
  49            for arg_name, diagnostic in diagnostic_by_arg.items():
  50                if expression.args.get(arg_name):
  51                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
  52                        arg_name, expression_name, dialect_name
  53                    )
  54                    generator.unsupported(diagnostic)
  55
  56            return func(generator, expression)
  57
  58        return _func
  59
  60    return decorator
  61
  62
  63class _Generator(type):
  64    def __new__(cls, clsname, bases, attrs):
  65        klass = super().__new__(cls, clsname, bases, attrs)
  66
  67        # Remove transforms that correspond to unsupported JSONPathPart expressions
  68        for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS:
  69            klass.TRANSFORMS.pop(part, None)
  70
  71        return klass
  72
  73
  74class Generator(metaclass=_Generator):
  75    """
  76    Generator converts a given syntax tree to the corresponding SQL string.
  77
  78    Args:
  79        pretty: Whether to format the produced SQL string.
  80            Default: False.
  81        identify: Determines when an identifier should be quoted. Possible values are:
  82            False (default): Never quote, except in cases where it's mandatory by the dialect.
  83            True or 'always': Always quote.
  84            'safe': Only quote identifiers that are case insensitive.
  85        normalize: Whether to normalize identifiers to lowercase.
  86            Default: False.
  87        pad: The pad size in a formatted string. For example, this affects the indentation of
  88            a projection in a query, relative to its nesting level.
  89            Default: 2.
  90        indent: The indentation size in a formatted string. For example, this affects the
  91            indentation of subqueries and filters under a `WHERE` clause.
  92            Default: 2.
  93        normalize_functions: How to normalize function names. Possible values are:
  94            "upper" or True (default): Convert names to uppercase.
  95            "lower": Convert names to lowercase.
  96            False: Disables function name normalization.
  97        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
  98            Default ErrorLevel.WARN.
  99        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 100            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 101            Default: 3
 102        leading_comma: Whether the comma is leading or trailing in select expressions.
 103            This is only relevant when generating in pretty mode.
 104            Default: False
 105        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 106            The default is on the smaller end because the length only represents a segment and not the true
 107            line length.
 108            Default: 80
 109        comments: Whether to preserve comments in the output SQL code.
 110            Default: True
 111    """
 112
 113    TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = {
 114        **JSON_PATH_PART_TRANSFORMS,
 115        exp.AllowedValuesProperty: lambda self,
 116        e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}",
 117        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 118        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 119        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 120        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 121        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 122        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 123        exp.CaseSpecificColumnConstraint: lambda _,
 124        e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC",
 125        exp.Ceil: lambda self, e: self.ceil_floor(e),
 126        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 127        exp.CharacterSetProperty: lambda self,
 128        e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}",
 129        exp.ClusteredColumnConstraint: lambda self,
 130        e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})",
 131        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 132        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 133        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 134        exp.ConvertToCharset: lambda self, e: self.func(
 135            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 136        ),
 137        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 138        exp.CredentialsProperty: lambda self,
 139        e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})",
 140        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 141        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 142        exp.DynamicProperty: lambda *_: "DYNAMIC",
 143        exp.EmptyProperty: lambda *_: "EMPTY",
 144        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 145        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 146        exp.EphemeralColumnConstraint: lambda self,
 147        e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}",
 148        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 149        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 150        exp.Except: lambda self, e: self.set_operations(e),
 151        exp.ExternalProperty: lambda *_: "EXTERNAL",
 152        exp.Floor: lambda self, e: self.ceil_floor(e),
 153        exp.Get: lambda self, e: self.get_put_sql(e),
 154        exp.GlobalProperty: lambda *_: "GLOBAL",
 155        exp.HeapProperty: lambda *_: "HEAP",
 156        exp.IcebergProperty: lambda *_: "ICEBERG",
 157        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 158        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 159        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 160        exp.Intersect: lambda self, e: self.set_operations(e),
 161        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 162        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)),
 163        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 164        exp.LocationProperty: lambda self, e: self.naked_property(e),
 165        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 166        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 167        exp.NonClusteredColumnConstraint: lambda self,
 168        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 169        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 170        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 171        exp.OnCommitProperty: lambda _,
 172        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 173        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 174        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 175        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 176        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 177        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 178        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 179        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 180        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 181        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 182        exp.ProjectionPolicyColumnConstraint: lambda self,
 183        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 184        exp.Put: lambda self, e: self.get_put_sql(e),
 185        exp.RemoteWithConnectionModelProperty: lambda self,
 186        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 187        exp.ReturnsProperty: lambda self, e: (
 188            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 189        ),
 190        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 191        exp.SecureProperty: lambda *_: "SECURE",
 192        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 193        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 194        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 195        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 196        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 197        exp.SqlReadWriteProperty: lambda _, e: e.name,
 198        exp.SqlSecurityProperty: lambda _,
 199        e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
 200        exp.StabilityProperty: lambda _, e: e.name,
 201        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 202        exp.StreamingTableProperty: lambda *_: "STREAMING",
 203        exp.StrictProperty: lambda *_: "STRICT",
 204        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 205        exp.TableColumn: lambda self, e: self.sql(e.this),
 206        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 207        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 208        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 209        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 210        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 211        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 212        exp.TransientProperty: lambda *_: "TRANSIENT",
 213        exp.Union: lambda self, e: self.set_operations(e),
 214        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 215        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 216        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 217        exp.Uuid: lambda *_: "UUID()",
 218        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 219        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 220        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 221        exp.VolatileProperty: lambda *_: "VOLATILE",
 222        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 223        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 224        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 225        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 226        exp.ForceProperty: lambda *_: "FORCE",
 227    }
 228
 229    # Whether null ordering is supported in order by
 230    # True: Full Support, None: No support, False: No support for certain cases
 231    # such as window specifications, aggregate functions etc
 232    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 233
 234    # Whether ignore nulls is inside the agg or outside.
 235    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 236    IGNORE_NULLS_IN_FUNC = False
 237
 238    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 239    LOCKING_READS_SUPPORTED = False
 240
 241    # Whether the EXCEPT and INTERSECT operations can return duplicates
 242    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 243
 244    # Wrap derived values in parens, usually standard but spark doesn't support it
 245    WRAP_DERIVED_VALUES = True
 246
 247    # Whether create function uses an AS before the RETURN
 248    CREATE_FUNCTION_RETURN_AS = True
 249
 250    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 251    MATCHED_BY_SOURCE = True
 252
 253    # Whether the INTERVAL expression works only with values like '1 day'
 254    SINGLE_STRING_INTERVAL = False
 255
 256    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 257    INTERVAL_ALLOWS_PLURAL_FORM = True
 258
 259    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 260    LIMIT_FETCH = "ALL"
 261
 262    # Whether limit and fetch allows expresions or just limits
 263    LIMIT_ONLY_LITERALS = False
 264
 265    # Whether a table is allowed to be renamed with a db
 266    RENAME_TABLE_WITH_DB = True
 267
 268    # The separator for grouping sets and rollups
 269    GROUPINGS_SEP = ","
 270
 271    # The string used for creating an index on a table
 272    INDEX_ON = "ON"
 273
 274    # Whether join hints should be generated
 275    JOIN_HINTS = True
 276
 277    # Whether table hints should be generated
 278    TABLE_HINTS = True
 279
 280    # Whether query hints should be generated
 281    QUERY_HINTS = True
 282
 283    # What kind of separator to use for query hints
 284    QUERY_HINT_SEP = ", "
 285
 286    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 287    IS_BOOL_ALLOWED = True
 288
 289    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 290    DUPLICATE_KEY_UPDATE_WITH_SET = True
 291
 292    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 293    LIMIT_IS_TOP = False
 294
 295    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 296    RETURNING_END = True
 297
 298    # Whether to generate an unquoted value for EXTRACT's date part argument
 299    EXTRACT_ALLOWS_QUOTES = True
 300
 301    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 302    TZ_TO_WITH_TIME_ZONE = False
 303
 304    # Whether the NVL2 function is supported
 305    NVL2_SUPPORTED = True
 306
 307    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 308    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 309
 310    # Whether VALUES statements can be used as derived tables.
 311    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 312    # SELECT * VALUES into SELECT UNION
 313    VALUES_AS_TABLE = True
 314
 315    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 316    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 317
 318    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 319    UNNEST_WITH_ORDINALITY = True
 320
 321    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 322    AGGREGATE_FILTER_SUPPORTED = True
 323
 324    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 325    SEMI_ANTI_JOIN_WITH_SIDE = True
 326
 327    # Whether to include the type of a computed column in the CREATE DDL
 328    COMPUTED_COLUMN_WITH_TYPE = True
 329
 330    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 331    SUPPORTS_TABLE_COPY = True
 332
 333    # Whether parentheses are required around the table sample's expression
 334    TABLESAMPLE_REQUIRES_PARENS = True
 335
 336    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 337    TABLESAMPLE_SIZE_IS_ROWS = True
 338
 339    # The keyword(s) to use when generating a sample clause
 340    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 341
 342    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 343    TABLESAMPLE_WITH_METHOD = True
 344
 345    # The keyword to use when specifying the seed of a sample clause
 346    TABLESAMPLE_SEED_KEYWORD = "SEED"
 347
 348    # Whether COLLATE is a function instead of a binary operator
 349    COLLATE_IS_FUNC = False
 350
 351    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 352    DATA_TYPE_SPECIFIERS_ALLOWED = False
 353
 354    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 355    ENSURE_BOOLS = False
 356
 357    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 358    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 359
 360    # Whether CONCAT requires >1 arguments
 361    SUPPORTS_SINGLE_ARG_CONCAT = True
 362
 363    # Whether LAST_DAY function supports a date part argument
 364    LAST_DAY_SUPPORTS_DATE_PART = True
 365
 366    # Whether named columns are allowed in table aliases
 367    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 368
 369    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 370    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 371
 372    # What delimiter to use for separating JSON key/value pairs
 373    JSON_KEY_VALUE_PAIR_SEP = ":"
 374
 375    # INSERT OVERWRITE TABLE x override
 376    INSERT_OVERWRITE = " OVERWRITE TABLE"
 377
 378    # Whether the SELECT .. INTO syntax is used instead of CTAS
 379    SUPPORTS_SELECT_INTO = False
 380
 381    # Whether UNLOGGED tables can be created
 382    SUPPORTS_UNLOGGED_TABLES = False
 383
 384    # Whether the CREATE TABLE LIKE statement is supported
 385    SUPPORTS_CREATE_TABLE_LIKE = True
 386
 387    # Whether the LikeProperty needs to be specified inside of the schema clause
 388    LIKE_PROPERTY_INSIDE_SCHEMA = False
 389
 390    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 391    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 392    MULTI_ARG_DISTINCT = True
 393
 394    # Whether the JSON extraction operators expect a value of type JSON
 395    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 396
 397    # Whether bracketed keys like ["foo"] are supported in JSON paths
 398    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 399
 400    # Whether to escape keys using single quotes in JSON paths
 401    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 402
 403    # The JSONPathPart expressions supported by this dialect
 404    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 405
 406    # Whether any(f(x) for x in array) can be implemented by this dialect
 407    CAN_IMPLEMENT_ARRAY_ANY = False
 408
 409    # Whether the function TO_NUMBER is supported
 410    SUPPORTS_TO_NUMBER = True
 411
 412    # Whether EXCLUDE in window specification is supported
 413    SUPPORTS_WINDOW_EXCLUDE = False
 414
 415    # Whether or not set op modifiers apply to the outer set op or select.
 416    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 417    # True means limit 1 happens after the set op, False means it it happens on y.
 418    SET_OP_MODIFIERS = True
 419
 420    # Whether parameters from COPY statement are wrapped in parentheses
 421    COPY_PARAMS_ARE_WRAPPED = True
 422
 423    # Whether values of params are set with "=" token or empty space
 424    COPY_PARAMS_EQ_REQUIRED = False
 425
 426    # Whether COPY statement has INTO keyword
 427    COPY_HAS_INTO_KEYWORD = True
 428
 429    # Whether the conditional TRY(expression) function is supported
 430    TRY_SUPPORTED = True
 431
 432    # Whether the UESCAPE syntax in unicode strings is supported
 433    SUPPORTS_UESCAPE = True
 434
 435    # The keyword to use when generating a star projection with excluded columns
 436    STAR_EXCEPT = "EXCEPT"
 437
 438    # The HEX function name
 439    HEX_FUNC = "HEX"
 440
 441    # The keywords to use when prefixing & separating WITH based properties
 442    WITH_PROPERTIES_PREFIX = "WITH"
 443
 444    # Whether to quote the generated expression of exp.JsonPath
 445    QUOTE_JSON_PATH = True
 446
 447    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 448    PAD_FILL_PATTERN_IS_REQUIRED = False
 449
 450    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 451    SUPPORTS_EXPLODING_PROJECTIONS = True
 452
 453    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 454    ARRAY_CONCAT_IS_VAR_LEN = True
 455
 456    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 457    SUPPORTS_CONVERT_TIMEZONE = False
 458
 459    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 460    SUPPORTS_MEDIAN = True
 461
 462    # Whether UNIX_SECONDS(timestamp) is supported
 463    SUPPORTS_UNIX_SECONDS = False
 464
 465    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 466    ALTER_SET_WRAPPED = False
 467
 468    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 469    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 470    # TODO: The normalization should be done by default once we've tested it across all dialects.
 471    NORMALIZE_EXTRACT_DATE_PARTS = False
 472
 473    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 474    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 475
 476    # The function name of the exp.ArraySize expression
 477    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 478
 479    # The syntax to use when altering the type of a column
 480    ALTER_SET_TYPE = "SET DATA TYPE"
 481
 482    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 483    # None -> Doesn't support it at all
 484    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 485    # True (Postgres) -> Explicitly requires it
 486    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 487
 488    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 489    SUPPORTS_DECODE_CASE = True
 490
 491    TYPE_MAPPING = {
 492        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 493        exp.DataType.Type.NCHAR: "CHAR",
 494        exp.DataType.Type.NVARCHAR: "VARCHAR",
 495        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 496        exp.DataType.Type.LONGTEXT: "TEXT",
 497        exp.DataType.Type.TINYTEXT: "TEXT",
 498        exp.DataType.Type.BLOB: "VARBINARY",
 499        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 500        exp.DataType.Type.LONGBLOB: "BLOB",
 501        exp.DataType.Type.TINYBLOB: "BLOB",
 502        exp.DataType.Type.INET: "INET",
 503        exp.DataType.Type.ROWVERSION: "VARBINARY",
 504        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 505    }
 506
 507    TIME_PART_SINGULARS = {
 508        "MICROSECONDS": "MICROSECOND",
 509        "SECONDS": "SECOND",
 510        "MINUTES": "MINUTE",
 511        "HOURS": "HOUR",
 512        "DAYS": "DAY",
 513        "WEEKS": "WEEK",
 514        "MONTHS": "MONTH",
 515        "QUARTERS": "QUARTER",
 516        "YEARS": "YEAR",
 517    }
 518
 519    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 520        "cluster": lambda self, e: self.sql(e, "cluster"),
 521        "distribute": lambda self, e: self.sql(e, "distribute"),
 522        "sort": lambda self, e: self.sql(e, "sort"),
 523        "windows": lambda self, e: (
 524            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 525            if e.args.get("windows")
 526            else ""
 527        ),
 528        "qualify": lambda self, e: self.sql(e, "qualify"),
 529    }
 530
 531    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 532
 533    STRUCT_DELIMITER = ("<", ">")
 534
 535    PARAMETER_TOKEN = "@"
 536    NAMED_PLACEHOLDER_TOKEN = ":"
 537
 538    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 539
 540    PROPERTIES_LOCATION = {
 541        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 542        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 543        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 544        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 545        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 546        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 547        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 548        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 549        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 550        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 551        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 552        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 553        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 554        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 555        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 556        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 557        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 558        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 559        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 560        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 561        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 562        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 563        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 564        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 565        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 566        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 567        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 568        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 569        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 570        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 571        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 572        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 573        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 574        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 575        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 576        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 577        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 578        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 579        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 580        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 581        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 582        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 583        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 584        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 585        exp.LogProperty: exp.Properties.Location.POST_NAME,
 586        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 587        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 588        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 589        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 590        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 591        exp.Order: exp.Properties.Location.POST_SCHEMA,
 592        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 593        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 594        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 595        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 596        exp.Property: exp.Properties.Location.POST_WITH,
 597        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 599        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 600        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 602        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 604        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 605        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 607        exp.Set: exp.Properties.Location.POST_SCHEMA,
 608        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 609        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 610        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 611        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 612        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 613        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 614        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 615        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 616        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 617        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 618        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 619        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 620        exp.Tags: exp.Properties.Location.POST_WITH,
 621        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 622        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 623        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 624        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 625        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 626        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 627        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 628        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 629        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 630        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 631        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 632        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 633        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 634        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 635        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 636    }
 637
 638    # Keywords that can't be used as unquoted identifier names
 639    RESERVED_KEYWORDS: t.Set[str] = set()
 640
 641    # Expressions whose comments are separated from them for better formatting
 642    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 643        exp.Command,
 644        exp.Create,
 645        exp.Describe,
 646        exp.Delete,
 647        exp.Drop,
 648        exp.From,
 649        exp.Insert,
 650        exp.Join,
 651        exp.MultitableInserts,
 652        exp.Order,
 653        exp.Group,
 654        exp.Having,
 655        exp.Select,
 656        exp.SetOperation,
 657        exp.Update,
 658        exp.Where,
 659        exp.With,
 660    )
 661
 662    # Expressions that should not have their comments generated in maybe_comment
 663    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 664        exp.Binary,
 665        exp.SetOperation,
 666    )
 667
 668    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 669    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 670        exp.Column,
 671        exp.Literal,
 672        exp.Neg,
 673        exp.Paren,
 674    )
 675
 676    PARAMETERIZABLE_TEXT_TYPES = {
 677        exp.DataType.Type.NVARCHAR,
 678        exp.DataType.Type.VARCHAR,
 679        exp.DataType.Type.CHAR,
 680        exp.DataType.Type.NCHAR,
 681    }
 682
 683    # Expressions that need to have all CTEs under them bubbled up to them
 684    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 685
 686    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 687
 688    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 689
 690    __slots__ = (
 691        "pretty",
 692        "identify",
 693        "normalize",
 694        "pad",
 695        "_indent",
 696        "normalize_functions",
 697        "unsupported_level",
 698        "max_unsupported",
 699        "leading_comma",
 700        "max_text_width",
 701        "comments",
 702        "dialect",
 703        "unsupported_messages",
 704        "_escaped_quote_end",
 705        "_escaped_identifier_end",
 706        "_next_name",
 707        "_identifier_start",
 708        "_identifier_end",
 709        "_quote_json_path_key_using_brackets",
 710    )
 711
 712    def __init__(
 713        self,
 714        pretty: t.Optional[bool] = None,
 715        identify: str | bool = False,
 716        normalize: bool = False,
 717        pad: int = 2,
 718        indent: int = 2,
 719        normalize_functions: t.Optional[str | bool] = None,
 720        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 721        max_unsupported: int = 3,
 722        leading_comma: bool = False,
 723        max_text_width: int = 80,
 724        comments: bool = True,
 725        dialect: DialectType = None,
 726    ):
 727        import sqlglot
 728        from sqlglot.dialects import Dialect
 729
 730        self.pretty = pretty if pretty is not None else sqlglot.pretty
 731        self.identify = identify
 732        self.normalize = normalize
 733        self.pad = pad
 734        self._indent = indent
 735        self.unsupported_level = unsupported_level
 736        self.max_unsupported = max_unsupported
 737        self.leading_comma = leading_comma
 738        self.max_text_width = max_text_width
 739        self.comments = comments
 740        self.dialect = Dialect.get_or_raise(dialect)
 741
 742        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 743        self.normalize_functions = (
 744            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 745        )
 746
 747        self.unsupported_messages: t.List[str] = []
 748        self._escaped_quote_end: str = (
 749            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 750        )
 751        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 752
 753        self._next_name = name_sequence("_t")
 754
 755        self._identifier_start = self.dialect.IDENTIFIER_START
 756        self._identifier_end = self.dialect.IDENTIFIER_END
 757
 758        self._quote_json_path_key_using_brackets = True
 759
 760    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 761        """
 762        Generates the SQL string corresponding to the given syntax tree.
 763
 764        Args:
 765            expression: The syntax tree.
 766            copy: Whether to copy the expression. The generator performs mutations so
 767                it is safer to copy.
 768
 769        Returns:
 770            The SQL string corresponding to `expression`.
 771        """
 772        if copy:
 773            expression = expression.copy()
 774
 775        expression = self.preprocess(expression)
 776
 777        self.unsupported_messages = []
 778        sql = self.sql(expression).strip()
 779
 780        if self.pretty:
 781            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 782
 783        if self.unsupported_level == ErrorLevel.IGNORE:
 784            return sql
 785
 786        if self.unsupported_level == ErrorLevel.WARN:
 787            for msg in self.unsupported_messages:
 788                logger.warning(msg)
 789        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 790            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 791
 792        return sql
 793
 794    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 795        """Apply generic preprocessing transformations to a given expression."""
 796        expression = self._move_ctes_to_top_level(expression)
 797
 798        if self.ENSURE_BOOLS:
 799            from sqlglot.transforms import ensure_bools
 800
 801            expression = ensure_bools(expression)
 802
 803        return expression
 804
 805    def _move_ctes_to_top_level(self, expression: E) -> E:
 806        if (
 807            not expression.parent
 808            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 809            and any(node.parent is not expression for node in expression.find_all(exp.With))
 810        ):
 811            from sqlglot.transforms import move_ctes_to_top_level
 812
 813            expression = move_ctes_to_top_level(expression)
 814        return expression
 815
 816    def unsupported(self, message: str) -> None:
 817        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 818            raise UnsupportedError(message)
 819        self.unsupported_messages.append(message)
 820
 821    def sep(self, sep: str = " ") -> str:
 822        return f"{sep.strip()}\n" if self.pretty else sep
 823
 824    def seg(self, sql: str, sep: str = " ") -> str:
 825        return f"{self.sep(sep)}{sql}"
 826
 827    def sanitize_comment(self, comment: str) -> str:
 828        comment = " " + comment if comment[0].strip() else comment
 829        comment = comment + " " if comment[-1].strip() else comment
 830
 831        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 832            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 833            comment = comment.replace("*/", "* /")
 834
 835        return comment
 836
 837    def maybe_comment(
 838        self,
 839        sql: str,
 840        expression: t.Optional[exp.Expression] = None,
 841        comments: t.Optional[t.List[str]] = None,
 842        separated: bool = False,
 843    ) -> str:
 844        comments = (
 845            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 846            if self.comments
 847            else None
 848        )
 849
 850        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 851            return sql
 852
 853        comments_sql = " ".join(
 854            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 855        )
 856
 857        if not comments_sql:
 858            return sql
 859
 860        comments_sql = self._replace_line_breaks(comments_sql)
 861
 862        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 863            return (
 864                f"{self.sep()}{comments_sql}{sql}"
 865                if not sql or sql[0].isspace()
 866                else f"{comments_sql}{self.sep()}{sql}"
 867            )
 868
 869        return f"{sql} {comments_sql}"
 870
 871    def wrap(self, expression: exp.Expression | str) -> str:
 872        this_sql = (
 873            self.sql(expression)
 874            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 875            else self.sql(expression, "this")
 876        )
 877        if not this_sql:
 878            return "()"
 879
 880        this_sql = self.indent(this_sql, level=1, pad=0)
 881        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 882
 883    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 884        original = self.identify
 885        self.identify = False
 886        result = func(*args, **kwargs)
 887        self.identify = original
 888        return result
 889
 890    def normalize_func(self, name: str) -> str:
 891        if self.normalize_functions == "upper" or self.normalize_functions is True:
 892            return name.upper()
 893        if self.normalize_functions == "lower":
 894            return name.lower()
 895        return name
 896
 897    def indent(
 898        self,
 899        sql: str,
 900        level: int = 0,
 901        pad: t.Optional[int] = None,
 902        skip_first: bool = False,
 903        skip_last: bool = False,
 904    ) -> str:
 905        if not self.pretty or not sql:
 906            return sql
 907
 908        pad = self.pad if pad is None else pad
 909        lines = sql.split("\n")
 910
 911        return "\n".join(
 912            (
 913                line
 914                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 915                else f"{' ' * (level * self._indent + pad)}{line}"
 916            )
 917            for i, line in enumerate(lines)
 918        )
 919
 920    def sql(
 921        self,
 922        expression: t.Optional[str | exp.Expression],
 923        key: t.Optional[str] = None,
 924        comment: bool = True,
 925    ) -> str:
 926        if not expression:
 927            return ""
 928
 929        if isinstance(expression, str):
 930            return expression
 931
 932        if key:
 933            value = expression.args.get(key)
 934            if value:
 935                return self.sql(value)
 936            return ""
 937
 938        transform = self.TRANSFORMS.get(expression.__class__)
 939
 940        if callable(transform):
 941            sql = transform(self, expression)
 942        elif isinstance(expression, exp.Expression):
 943            exp_handler_name = f"{expression.key}_sql"
 944
 945            if hasattr(self, exp_handler_name):
 946                sql = getattr(self, exp_handler_name)(expression)
 947            elif isinstance(expression, exp.Func):
 948                sql = self.function_fallback_sql(expression)
 949            elif isinstance(expression, exp.Property):
 950                sql = self.property_sql(expression)
 951            else:
 952                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 953        else:
 954            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 955
 956        return self.maybe_comment(sql, expression) if self.comments and comment else sql
 957
 958    def uncache_sql(self, expression: exp.Uncache) -> str:
 959        table = self.sql(expression, "this")
 960        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 961        return f"UNCACHE TABLE{exists_sql} {table}"
 962
 963    def cache_sql(self, expression: exp.Cache) -> str:
 964        lazy = " LAZY" if expression.args.get("lazy") else ""
 965        table = self.sql(expression, "this")
 966        options = expression.args.get("options")
 967        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 968        sql = self.sql(expression, "expression")
 969        sql = f" AS{self.sep()}{sql}" if sql else ""
 970        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
 971        return self.prepend_ctes(expression, sql)
 972
 973    def characterset_sql(self, expression: exp.CharacterSet) -> str:
 974        if isinstance(expression.parent, exp.Cast):
 975            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
 976        default = "DEFAULT " if expression.args.get("default") else ""
 977        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
 978
 979    def column_parts(self, expression: exp.Column) -> str:
 980        return ".".join(
 981            self.sql(part)
 982            for part in (
 983                expression.args.get("catalog"),
 984                expression.args.get("db"),
 985                expression.args.get("table"),
 986                expression.args.get("this"),
 987            )
 988            if part
 989        )
 990
 991    def column_sql(self, expression: exp.Column) -> str:
 992        join_mark = " (+)" if expression.args.get("join_mark") else ""
 993
 994        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 995            join_mark = ""
 996            self.unsupported("Outer join syntax using the (+) operator is not supported.")
 997
 998        return f"{self.column_parts(expression)}{join_mark}"
 999
1000    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1001        this = self.sql(expression, "this")
1002        this = f" {this}" if this else ""
1003        position = self.sql(expression, "position")
1004        return f"{position}{this}"
1005
1006    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1007        column = self.sql(expression, "this")
1008        kind = self.sql(expression, "kind")
1009        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1010        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1011        kind = f"{sep}{kind}" if kind else ""
1012        constraints = f" {constraints}" if constraints else ""
1013        position = self.sql(expression, "position")
1014        position = f" {position}" if position else ""
1015
1016        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1017            kind = ""
1018
1019        return f"{exists}{column}{kind}{constraints}{position}"
1020
1021    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1022        this = self.sql(expression, "this")
1023        kind_sql = self.sql(expression, "kind").strip()
1024        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1025
1026    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1027        this = self.sql(expression, "this")
1028        if expression.args.get("not_null"):
1029            persisted = " PERSISTED NOT NULL"
1030        elif expression.args.get("persisted"):
1031            persisted = " PERSISTED"
1032        else:
1033            persisted = ""
1034
1035        return f"AS {this}{persisted}"
1036
1037    def autoincrementcolumnconstraint_sql(self, _) -> str:
1038        return self.token_sql(TokenType.AUTO_INCREMENT)
1039
1040    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1041        if isinstance(expression.this, list):
1042            this = self.wrap(self.expressions(expression, key="this", flat=True))
1043        else:
1044            this = self.sql(expression, "this")
1045
1046        return f"COMPRESS {this}"
1047
1048    def generatedasidentitycolumnconstraint_sql(
1049        self, expression: exp.GeneratedAsIdentityColumnConstraint
1050    ) -> str:
1051        this = ""
1052        if expression.this is not None:
1053            on_null = " ON NULL" if expression.args.get("on_null") else ""
1054            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1055
1056        start = expression.args.get("start")
1057        start = f"START WITH {start}" if start else ""
1058        increment = expression.args.get("increment")
1059        increment = f" INCREMENT BY {increment}" if increment else ""
1060        minvalue = expression.args.get("minvalue")
1061        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1062        maxvalue = expression.args.get("maxvalue")
1063        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1064        cycle = expression.args.get("cycle")
1065        cycle_sql = ""
1066
1067        if cycle is not None:
1068            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1069            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1070
1071        sequence_opts = ""
1072        if start or increment or cycle_sql:
1073            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1074            sequence_opts = f" ({sequence_opts.strip()})"
1075
1076        expr = self.sql(expression, "expression")
1077        expr = f"({expr})" if expr else "IDENTITY"
1078
1079        return f"GENERATED{this} AS {expr}{sequence_opts}"
1080
1081    def generatedasrowcolumnconstraint_sql(
1082        self, expression: exp.GeneratedAsRowColumnConstraint
1083    ) -> str:
1084        start = "START" if expression.args.get("start") else "END"
1085        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1086        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1087
1088    def periodforsystemtimeconstraint_sql(
1089        self, expression: exp.PeriodForSystemTimeConstraint
1090    ) -> str:
1091        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1092
1093    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1094        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1095
1096    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1097        desc = expression.args.get("desc")
1098        if desc is not None:
1099            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1100        options = self.expressions(expression, key="options", flat=True, sep=" ")
1101        options = f" {options}" if options else ""
1102        return f"PRIMARY KEY{options}"
1103
1104    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1105        this = self.sql(expression, "this")
1106        this = f" {this}" if this else ""
1107        index_type = expression.args.get("index_type")
1108        index_type = f" USING {index_type}" if index_type else ""
1109        on_conflict = self.sql(expression, "on_conflict")
1110        on_conflict = f" {on_conflict}" if on_conflict else ""
1111        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1112        options = self.expressions(expression, key="options", flat=True, sep=" ")
1113        options = f" {options}" if options else ""
1114        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1115
1116    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1117        return self.sql(expression, "this")
1118
1119    def create_sql(self, expression: exp.Create) -> str:
1120        kind = self.sql(expression, "kind")
1121        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1122        properties = expression.args.get("properties")
1123        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1124
1125        this = self.createable_sql(expression, properties_locs)
1126
1127        properties_sql = ""
1128        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1129            exp.Properties.Location.POST_WITH
1130        ):
1131            properties_sql = self.sql(
1132                exp.Properties(
1133                    expressions=[
1134                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
1135                        *properties_locs[exp.Properties.Location.POST_WITH],
1136                    ]
1137                )
1138            )
1139
1140            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1141                properties_sql = self.sep() + properties_sql
1142            elif not self.pretty:
1143                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1144                properties_sql = f" {properties_sql}"
1145
1146        begin = " BEGIN" if expression.args.get("begin") else ""
1147        end = " END" if expression.args.get("end") else ""
1148
1149        expression_sql = self.sql(expression, "expression")
1150        if expression_sql:
1151            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1152
1153            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1154                postalias_props_sql = ""
1155                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1156                    postalias_props_sql = self.properties(
1157                        exp.Properties(
1158                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1159                        ),
1160                        wrapped=False,
1161                    )
1162                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1163                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1164
1165        postindex_props_sql = ""
1166        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1167            postindex_props_sql = self.properties(
1168                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1169                wrapped=False,
1170                prefix=" ",
1171            )
1172
1173        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1174        indexes = f" {indexes}" if indexes else ""
1175        index_sql = indexes + postindex_props_sql
1176
1177        replace = " OR REPLACE" if expression.args.get("replace") else ""
1178        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1179        unique = " UNIQUE" if expression.args.get("unique") else ""
1180
1181        clustered = expression.args.get("clustered")
1182        if clustered is None:
1183            clustered_sql = ""
1184        elif clustered:
1185            clustered_sql = " CLUSTERED COLUMNSTORE"
1186        else:
1187            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1188
1189        postcreate_props_sql = ""
1190        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1191            postcreate_props_sql = self.properties(
1192                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1193                sep=" ",
1194                prefix=" ",
1195                wrapped=False,
1196            )
1197
1198        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1199
1200        postexpression_props_sql = ""
1201        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1202            postexpression_props_sql = self.properties(
1203                exp.Properties(
1204                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1205                ),
1206                sep=" ",
1207                prefix=" ",
1208                wrapped=False,
1209            )
1210
1211        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1212        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1213        no_schema_binding = (
1214            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1215        )
1216
1217        clone = self.sql(expression, "clone")
1218        clone = f" {clone}" if clone else ""
1219
1220        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1221            properties_expression = f"{expression_sql}{properties_sql}"
1222        else:
1223            properties_expression = f"{properties_sql}{expression_sql}"
1224
1225        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1226        return self.prepend_ctes(expression, expression_sql)
1227
1228    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1229        start = self.sql(expression, "start")
1230        start = f"START WITH {start}" if start else ""
1231        increment = self.sql(expression, "increment")
1232        increment = f" INCREMENT BY {increment}" if increment else ""
1233        minvalue = self.sql(expression, "minvalue")
1234        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1235        maxvalue = self.sql(expression, "maxvalue")
1236        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1237        owned = self.sql(expression, "owned")
1238        owned = f" OWNED BY {owned}" if owned else ""
1239
1240        cache = expression.args.get("cache")
1241        if cache is None:
1242            cache_str = ""
1243        elif cache is True:
1244            cache_str = " CACHE"
1245        else:
1246            cache_str = f" CACHE {cache}"
1247
1248        options = self.expressions(expression, key="options", flat=True, sep=" ")
1249        options = f" {options}" if options else ""
1250
1251        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1252
1253    def clone_sql(self, expression: exp.Clone) -> str:
1254        this = self.sql(expression, "this")
1255        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1256        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1257        return f"{shallow}{keyword} {this}"
1258
1259    def describe_sql(self, expression: exp.Describe) -> str:
1260        style = expression.args.get("style")
1261        style = f" {style}" if style else ""
1262        partition = self.sql(expression, "partition")
1263        partition = f" {partition}" if partition else ""
1264        format = self.sql(expression, "format")
1265        format = f" {format}" if format else ""
1266
1267        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1268
1269    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1270        tag = self.sql(expression, "tag")
1271        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1272
1273    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1274        with_ = self.sql(expression, "with")
1275        if with_:
1276            sql = f"{with_}{self.sep()}{sql}"
1277        return sql
1278
1279    def with_sql(self, expression: exp.With) -> str:
1280        sql = self.expressions(expression, flat=True)
1281        recursive = (
1282            "RECURSIVE "
1283            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1284            else ""
1285        )
1286        search = self.sql(expression, "search")
1287        search = f" {search}" if search else ""
1288
1289        return f"WITH {recursive}{sql}{search}"
1290
1291    def cte_sql(self, expression: exp.CTE) -> str:
1292        alias = expression.args.get("alias")
1293        if alias:
1294            alias.add_comments(expression.pop_comments())
1295
1296        alias_sql = self.sql(expression, "alias")
1297
1298        materialized = expression.args.get("materialized")
1299        if materialized is False:
1300            materialized = "NOT MATERIALIZED "
1301        elif materialized:
1302            materialized = "MATERIALIZED "
1303
1304        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1305
1306    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1307        alias = self.sql(expression, "this")
1308        columns = self.expressions(expression, key="columns", flat=True)
1309        columns = f"({columns})" if columns else ""
1310
1311        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1312            columns = ""
1313            self.unsupported("Named columns are not supported in table alias.")
1314
1315        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1316            alias = self._next_name()
1317
1318        return f"{alias}{columns}"
1319
1320    def bitstring_sql(self, expression: exp.BitString) -> str:
1321        this = self.sql(expression, "this")
1322        if self.dialect.BIT_START:
1323            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1324        return f"{int(this, 2)}"
1325
1326    def hexstring_sql(
1327        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1328    ) -> str:
1329        this = self.sql(expression, "this")
1330        is_integer_type = expression.args.get("is_integer")
1331
1332        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1333            not self.dialect.HEX_START and not binary_function_repr
1334        ):
1335            # Integer representation will be returned if:
1336            # - The read dialect treats the hex value as integer literal but not the write
1337            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1338            return f"{int(this, 16)}"
1339
1340        if not is_integer_type:
1341            # Read dialect treats the hex value as BINARY/BLOB
1342            if binary_function_repr:
1343                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1344                return self.func(binary_function_repr, exp.Literal.string(this))
1345            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1346                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1347                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1348
1349        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1350
1351    def bytestring_sql(self, expression: exp.ByteString) -> str:
1352        this = self.sql(expression, "this")
1353        if self.dialect.BYTE_START:
1354            return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1355        return this
1356
1357    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1358        this = self.sql(expression, "this")
1359        escape = expression.args.get("escape")
1360
1361        if self.dialect.UNICODE_START:
1362            escape_substitute = r"\\\1"
1363            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1364        else:
1365            escape_substitute = r"\\u\1"
1366            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1367
1368        if escape:
1369            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1370            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1371        else:
1372            escape_pattern = ESCAPED_UNICODE_RE
1373            escape_sql = ""
1374
1375        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1376            this = escape_pattern.sub(escape_substitute, this)
1377
1378        return f"{left_quote}{this}{right_quote}{escape_sql}"
1379
1380    def rawstring_sql(self, expression: exp.RawString) -> str:
1381        string = expression.this
1382        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1383            string = string.replace("\\", "\\\\")
1384
1385        string = self.escape_str(string, escape_backslash=False)
1386        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1387
1388    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1389        this = self.sql(expression, "this")
1390        specifier = self.sql(expression, "expression")
1391        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1392        return f"{this}{specifier}"
1393
1394    def datatype_sql(self, expression: exp.DataType) -> str:
1395        nested = ""
1396        values = ""
1397        interior = self.expressions(expression, flat=True)
1398
1399        type_value = expression.this
1400        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1401            type_sql = self.sql(expression, "kind")
1402        else:
1403            type_sql = (
1404                self.TYPE_MAPPING.get(type_value, type_value.value)
1405                if isinstance(type_value, exp.DataType.Type)
1406                else type_value
1407            )
1408
1409        if interior:
1410            if expression.args.get("nested"):
1411                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1412                if expression.args.get("values") is not None:
1413                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1414                    values = self.expressions(expression, key="values", flat=True)
1415                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1416            elif type_value == exp.DataType.Type.INTERVAL:
1417                nested = f" {interior}"
1418            else:
1419                nested = f"({interior})"
1420
1421        type_sql = f"{type_sql}{nested}{values}"
1422        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1423            exp.DataType.Type.TIMETZ,
1424            exp.DataType.Type.TIMESTAMPTZ,
1425        ):
1426            type_sql = f"{type_sql} WITH TIME ZONE"
1427
1428        return type_sql
1429
1430    def directory_sql(self, expression: exp.Directory) -> str:
1431        local = "LOCAL " if expression.args.get("local") else ""
1432        row_format = self.sql(expression, "row_format")
1433        row_format = f" {row_format}" if row_format else ""
1434        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1435
1436    def delete_sql(self, expression: exp.Delete) -> str:
1437        this = self.sql(expression, "this")
1438        this = f" FROM {this}" if this else ""
1439        using = self.sql(expression, "using")
1440        using = f" USING {using}" if using else ""
1441        cluster = self.sql(expression, "cluster")
1442        cluster = f" {cluster}" if cluster else ""
1443        where = self.sql(expression, "where")
1444        returning = self.sql(expression, "returning")
1445        limit = self.sql(expression, "limit")
1446        tables = self.expressions(expression, key="tables")
1447        tables = f" {tables}" if tables else ""
1448        if self.RETURNING_END:
1449            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1450        else:
1451            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1452        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1453
1454    def drop_sql(self, expression: exp.Drop) -> str:
1455        this = self.sql(expression, "this")
1456        expressions = self.expressions(expression, flat=True)
1457        expressions = f" ({expressions})" if expressions else ""
1458        kind = expression.args["kind"]
1459        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1460        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1461        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1462        on_cluster = self.sql(expression, "cluster")
1463        on_cluster = f" {on_cluster}" if on_cluster else ""
1464        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1465        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1466        cascade = " CASCADE" if expression.args.get("cascade") else ""
1467        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1468        purge = " PURGE" if expression.args.get("purge") else ""
1469        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1470
1471    def set_operation(self, expression: exp.SetOperation) -> str:
1472        op_type = type(expression)
1473        op_name = op_type.key.upper()
1474
1475        distinct = expression.args.get("distinct")
1476        if (
1477            distinct is False
1478            and op_type in (exp.Except, exp.Intersect)
1479            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1480        ):
1481            self.unsupported(f"{op_name} ALL is not supported")
1482
1483        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1484
1485        if distinct is None:
1486            distinct = default_distinct
1487            if distinct is None:
1488                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1489
1490        if distinct is default_distinct:
1491            distinct_or_all = ""
1492        else:
1493            distinct_or_all = " DISTINCT" if distinct else " ALL"
1494
1495        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1496        side_kind = f"{side_kind} " if side_kind else ""
1497
1498        by_name = " BY NAME" if expression.args.get("by_name") else ""
1499        on = self.expressions(expression, key="on", flat=True)
1500        on = f" ON ({on})" if on else ""
1501
1502        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1503
1504    def set_operations(self, expression: exp.SetOperation) -> str:
1505        if not self.SET_OP_MODIFIERS:
1506            limit = expression.args.get("limit")
1507            order = expression.args.get("order")
1508
1509            if limit or order:
1510                select = self._move_ctes_to_top_level(
1511                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1512                )
1513
1514                if limit:
1515                    select = select.limit(limit.pop(), copy=False)
1516                if order:
1517                    select = select.order_by(order.pop(), copy=False)
1518                return self.sql(select)
1519
1520        sqls: t.List[str] = []
1521        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1522
1523        while stack:
1524            node = stack.pop()
1525
1526            if isinstance(node, exp.SetOperation):
1527                stack.append(node.expression)
1528                stack.append(
1529                    self.maybe_comment(
1530                        self.set_operation(node), comments=node.comments, separated=True
1531                    )
1532                )
1533                stack.append(node.this)
1534            else:
1535                sqls.append(self.sql(node))
1536
1537        this = self.sep().join(sqls)
1538        this = self.query_modifiers(expression, this)
1539        return self.prepend_ctes(expression, this)
1540
1541    def fetch_sql(self, expression: exp.Fetch) -> str:
1542        direction = expression.args.get("direction")
1543        direction = f" {direction}" if direction else ""
1544        count = self.sql(expression, "count")
1545        count = f" {count}" if count else ""
1546        limit_options = self.sql(expression, "limit_options")
1547        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1548        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1549
1550    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1551        percent = " PERCENT" if expression.args.get("percent") else ""
1552        rows = " ROWS" if expression.args.get("rows") else ""
1553        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1554        if not with_ties and rows:
1555            with_ties = " ONLY"
1556        return f"{percent}{rows}{with_ties}"
1557
1558    def filter_sql(self, expression: exp.Filter) -> str:
1559        if self.AGGREGATE_FILTER_SUPPORTED:
1560            this = self.sql(expression, "this")
1561            where = self.sql(expression, "expression").strip()
1562            return f"{this} FILTER({where})"
1563
1564        agg = expression.this
1565        agg_arg = agg.this
1566        cond = expression.expression.this
1567        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1568        return self.sql(agg)
1569
1570    def hint_sql(self, expression: exp.Hint) -> str:
1571        if not self.QUERY_HINTS:
1572            self.unsupported("Hints are not supported")
1573            return ""
1574
1575        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1576
1577    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1578        using = self.sql(expression, "using")
1579        using = f" USING {using}" if using else ""
1580        columns = self.expressions(expression, key="columns", flat=True)
1581        columns = f"({columns})" if columns else ""
1582        partition_by = self.expressions(expression, key="partition_by", flat=True)
1583        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1584        where = self.sql(expression, "where")
1585        include = self.expressions(expression, key="include", flat=True)
1586        if include:
1587            include = f" INCLUDE ({include})"
1588        with_storage = self.expressions(expression, key="with_storage", flat=True)
1589        with_storage = f" WITH ({with_storage})" if with_storage else ""
1590        tablespace = self.sql(expression, "tablespace")
1591        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1592        on = self.sql(expression, "on")
1593        on = f" ON {on}" if on else ""
1594
1595        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1596
1597    def index_sql(self, expression: exp.Index) -> str:
1598        unique = "UNIQUE " if expression.args.get("unique") else ""
1599        primary = "PRIMARY " if expression.args.get("primary") else ""
1600        amp = "AMP " if expression.args.get("amp") else ""
1601        name = self.sql(expression, "this")
1602        name = f"{name} " if name else ""
1603        table = self.sql(expression, "table")
1604        table = f"{self.INDEX_ON} {table}" if table else ""
1605
1606        index = "INDEX " if not table else ""
1607
1608        params = self.sql(expression, "params")
1609        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1610
1611    def identifier_sql(self, expression: exp.Identifier) -> str:
1612        text = expression.name
1613        lower = text.lower()
1614        text = lower if self.normalize and not expression.quoted else text
1615        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1616        if (
1617            expression.quoted
1618            or self.dialect.can_identify(text, self.identify)
1619            or lower in self.RESERVED_KEYWORDS
1620            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1621        ):
1622            text = f"{self._identifier_start}{text}{self._identifier_end}"
1623        return text
1624
1625    def hex_sql(self, expression: exp.Hex) -> str:
1626        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1627        if self.dialect.HEX_LOWERCASE:
1628            text = self.func("LOWER", text)
1629
1630        return text
1631
1632    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1633        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1634        if not self.dialect.HEX_LOWERCASE:
1635            text = self.func("LOWER", text)
1636        return text
1637
1638    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1639        input_format = self.sql(expression, "input_format")
1640        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1641        output_format = self.sql(expression, "output_format")
1642        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1643        return self.sep().join((input_format, output_format))
1644
1645    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1646        string = self.sql(exp.Literal.string(expression.name))
1647        return f"{prefix}{string}"
1648
1649    def partition_sql(self, expression: exp.Partition) -> str:
1650        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1651        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1652
1653    def properties_sql(self, expression: exp.Properties) -> str:
1654        root_properties = []
1655        with_properties = []
1656
1657        for p in expression.expressions:
1658            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1659            if p_loc == exp.Properties.Location.POST_WITH:
1660                with_properties.append(p)
1661            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1662                root_properties.append(p)
1663
1664        root_props = self.root_properties(exp.Properties(expressions=root_properties))
1665        with_props = self.with_properties(exp.Properties(expressions=with_properties))
1666
1667        if root_props and with_props and not self.pretty:
1668            with_props = " " + with_props
1669
1670        return root_props + with_props
1671
1672    def root_properties(self, properties: exp.Properties) -> str:
1673        if properties.expressions:
1674            return self.expressions(properties, indent=False, sep=" ")
1675        return ""
1676
1677    def properties(
1678        self,
1679        properties: exp.Properties,
1680        prefix: str = "",
1681        sep: str = ", ",
1682        suffix: str = "",
1683        wrapped: bool = True,
1684    ) -> str:
1685        if properties.expressions:
1686            expressions = self.expressions(properties, sep=sep, indent=False)
1687            if expressions:
1688                expressions = self.wrap(expressions) if wrapped else expressions
1689                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1690        return ""
1691
1692    def with_properties(self, properties: exp.Properties) -> str:
1693        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1694
1695    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1696        properties_locs = defaultdict(list)
1697        for p in properties.expressions:
1698            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1699            if p_loc != exp.Properties.Location.UNSUPPORTED:
1700                properties_locs[p_loc].append(p)
1701            else:
1702                self.unsupported(f"Unsupported property {p.key}")
1703
1704        return properties_locs
1705
1706    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1707        if isinstance(expression.this, exp.Dot):
1708            return self.sql(expression, "this")
1709        return f"'{expression.name}'" if string_key else expression.name
1710
1711    def property_sql(self, expression: exp.Property) -> str:
1712        property_cls = expression.__class__
1713        if property_cls == exp.Property:
1714            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1715
1716        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1717        if not property_name:
1718            self.unsupported(f"Unsupported property {expression.key}")
1719
1720        return f"{property_name}={self.sql(expression, 'this')}"
1721
1722    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1723        if self.SUPPORTS_CREATE_TABLE_LIKE:
1724            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1725            options = f" {options}" if options else ""
1726
1727            like = f"LIKE {self.sql(expression, 'this')}{options}"
1728            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1729                like = f"({like})"
1730
1731            return like
1732
1733        if expression.expressions:
1734            self.unsupported("Transpilation of LIKE property options is unsupported")
1735
1736        select = exp.select("*").from_(expression.this).limit(0)
1737        return f"AS {self.sql(select)}"
1738
1739    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1740        no = "NO " if expression.args.get("no") else ""
1741        protection = " PROTECTION" if expression.args.get("protection") else ""
1742        return f"{no}FALLBACK{protection}"
1743
1744    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1745        no = "NO " if expression.args.get("no") else ""
1746        local = expression.args.get("local")
1747        local = f"{local} " if local else ""
1748        dual = "DUAL " if expression.args.get("dual") else ""
1749        before = "BEFORE " if expression.args.get("before") else ""
1750        after = "AFTER " if expression.args.get("after") else ""
1751        return f"{no}{local}{dual}{before}{after}JOURNAL"
1752
1753    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1754        freespace = self.sql(expression, "this")
1755        percent = " PERCENT" if expression.args.get("percent") else ""
1756        return f"FREESPACE={freespace}{percent}"
1757
1758    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1759        if expression.args.get("default"):
1760            property = "DEFAULT"
1761        elif expression.args.get("on"):
1762            property = "ON"
1763        else:
1764            property = "OFF"
1765        return f"CHECKSUM={property}"
1766
1767    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1768        if expression.args.get("no"):
1769            return "NO MERGEBLOCKRATIO"
1770        if expression.args.get("default"):
1771            return "DEFAULT MERGEBLOCKRATIO"
1772
1773        percent = " PERCENT" if expression.args.get("percent") else ""
1774        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1775
1776    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1777        default = expression.args.get("default")
1778        minimum = expression.args.get("minimum")
1779        maximum = expression.args.get("maximum")
1780        if default or minimum or maximum:
1781            if default:
1782                prop = "DEFAULT"
1783            elif minimum:
1784                prop = "MINIMUM"
1785            else:
1786                prop = "MAXIMUM"
1787            return f"{prop} DATABLOCKSIZE"
1788        units = expression.args.get("units")
1789        units = f" {units}" if units else ""
1790        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1791
1792    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1793        autotemp = expression.args.get("autotemp")
1794        always = expression.args.get("always")
1795        default = expression.args.get("default")
1796        manual = expression.args.get("manual")
1797        never = expression.args.get("never")
1798
1799        if autotemp is not None:
1800            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1801        elif always:
1802            prop = "ALWAYS"
1803        elif default:
1804            prop = "DEFAULT"
1805        elif manual:
1806            prop = "MANUAL"
1807        elif never:
1808            prop = "NEVER"
1809        return f"BLOCKCOMPRESSION={prop}"
1810
1811    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1812        no = expression.args.get("no")
1813        no = " NO" if no else ""
1814        concurrent = expression.args.get("concurrent")
1815        concurrent = " CONCURRENT" if concurrent else ""
1816        target = self.sql(expression, "target")
1817        target = f" {target}" if target else ""
1818        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1819
1820    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1821        if isinstance(expression.this, list):
1822            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1823        if expression.this:
1824            modulus = self.sql(expression, "this")
1825            remainder = self.sql(expression, "expression")
1826            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1827
1828        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1829        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1830        return f"FROM ({from_expressions}) TO ({to_expressions})"
1831
1832    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1833        this = self.sql(expression, "this")
1834
1835        for_values_or_default = expression.expression
1836        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1837            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1838        else:
1839            for_values_or_default = " DEFAULT"
1840
1841        return f"PARTITION OF {this}{for_values_or_default}"
1842
1843    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1844        kind = expression.args.get("kind")
1845        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1846        for_or_in = expression.args.get("for_or_in")
1847        for_or_in = f" {for_or_in}" if for_or_in else ""
1848        lock_type = expression.args.get("lock_type")
1849        override = " OVERRIDE" if expression.args.get("override") else ""
1850        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1851
1852    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1853        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1854        statistics = expression.args.get("statistics")
1855        statistics_sql = ""
1856        if statistics is not None:
1857            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1858        return f"{data_sql}{statistics_sql}"
1859
1860    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1861        this = self.sql(expression, "this")
1862        this = f"HISTORY_TABLE={this}" if this else ""
1863        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1864        data_consistency = (
1865            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1866        )
1867        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1868        retention_period = (
1869            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1870        )
1871
1872        if this:
1873            on_sql = self.func("ON", this, data_consistency, retention_period)
1874        else:
1875            on_sql = "ON" if expression.args.get("on") else "OFF"
1876
1877        sql = f"SYSTEM_VERSIONING={on_sql}"
1878
1879        return f"WITH({sql})" if expression.args.get("with") else sql
1880
1881    def insert_sql(self, expression: exp.Insert) -> str:
1882        hint = self.sql(expression, "hint")
1883        overwrite = expression.args.get("overwrite")
1884
1885        if isinstance(expression.this, exp.Directory):
1886            this = " OVERWRITE" if overwrite else " INTO"
1887        else:
1888            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1889
1890        stored = self.sql(expression, "stored")
1891        stored = f" {stored}" if stored else ""
1892        alternative = expression.args.get("alternative")
1893        alternative = f" OR {alternative}" if alternative else ""
1894        ignore = " IGNORE" if expression.args.get("ignore") else ""
1895        is_function = expression.args.get("is_function")
1896        if is_function:
1897            this = f"{this} FUNCTION"
1898        this = f"{this} {self.sql(expression, 'this')}"
1899
1900        exists = " IF EXISTS" if expression.args.get("exists") else ""
1901        where = self.sql(expression, "where")
1902        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1903        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1904        on_conflict = self.sql(expression, "conflict")
1905        on_conflict = f" {on_conflict}" if on_conflict else ""
1906        by_name = " BY NAME" if expression.args.get("by_name") else ""
1907        returning = self.sql(expression, "returning")
1908
1909        if self.RETURNING_END:
1910            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1911        else:
1912            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1913
1914        partition_by = self.sql(expression, "partition")
1915        partition_by = f" {partition_by}" if partition_by else ""
1916        settings = self.sql(expression, "settings")
1917        settings = f" {settings}" if settings else ""
1918
1919        source = self.sql(expression, "source")
1920        source = f"TABLE {source}" if source else ""
1921
1922        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1923        return self.prepend_ctes(expression, sql)
1924
1925    def introducer_sql(self, expression: exp.Introducer) -> str:
1926        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1927
1928    def kill_sql(self, expression: exp.Kill) -> str:
1929        kind = self.sql(expression, "kind")
1930        kind = f" {kind}" if kind else ""
1931        this = self.sql(expression, "this")
1932        this = f" {this}" if this else ""
1933        return f"KILL{kind}{this}"
1934
1935    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1936        return expression.name
1937
1938    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1939        return expression.name
1940
1941    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1942        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1943
1944        constraint = self.sql(expression, "constraint")
1945        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1946
1947        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1948        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1949        action = self.sql(expression, "action")
1950
1951        expressions = self.expressions(expression, flat=True)
1952        if expressions:
1953            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1954            expressions = f" {set_keyword}{expressions}"
1955
1956        where = self.sql(expression, "where")
1957        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
1958
1959    def returning_sql(self, expression: exp.Returning) -> str:
1960        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
1961
1962    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1963        fields = self.sql(expression, "fields")
1964        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1965        escaped = self.sql(expression, "escaped")
1966        escaped = f" ESCAPED BY {escaped}" if escaped else ""
1967        items = self.sql(expression, "collection_items")
1968        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1969        keys = self.sql(expression, "map_keys")
1970        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1971        lines = self.sql(expression, "lines")
1972        lines = f" LINES TERMINATED BY {lines}" if lines else ""
1973        null = self.sql(expression, "null")
1974        null = f" NULL DEFINED AS {null}" if null else ""
1975        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1976
1977    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1978        return f"WITH ({self.expressions(expression, flat=True)})"
1979
1980    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1981        this = f"{self.sql(expression, 'this')} INDEX"
1982        target = self.sql(expression, "target")
1983        target = f" FOR {target}" if target else ""
1984        return f"{this}{target} ({self.expressions(expression, flat=True)})"
1985
1986    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
1987        this = self.sql(expression, "this")
1988        kind = self.sql(expression, "kind")
1989        expr = self.sql(expression, "expression")
1990        return f"{this} ({kind} => {expr})"
1991
1992    def table_parts(self, expression: exp.Table) -> str:
1993        return ".".join(
1994            self.sql(part)
1995            for part in (
1996                expression.args.get("catalog"),
1997                expression.args.get("db"),
1998                expression.args.get("this"),
1999            )
2000            if part is not None
2001        )
2002
2003    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2004        table = self.table_parts(expression)
2005        only = "ONLY " if expression.args.get("only") else ""
2006        partition = self.sql(expression, "partition")
2007        partition = f" {partition}" if partition else ""
2008        version = self.sql(expression, "version")
2009        version = f" {version}" if version else ""
2010        alias = self.sql(expression, "alias")
2011        alias = f"{sep}{alias}" if alias else ""
2012
2013        sample = self.sql(expression, "sample")
2014        if self.dialect.ALIAS_POST_TABLESAMPLE:
2015            sample_pre_alias = sample
2016            sample_post_alias = ""
2017        else:
2018            sample_pre_alias = ""
2019            sample_post_alias = sample
2020
2021        hints = self.expressions(expression, key="hints", sep=" ")
2022        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2023        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2024        joins = self.indent(
2025            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2026        )
2027        laterals = self.expressions(expression, key="laterals", sep="")
2028
2029        file_format = self.sql(expression, "format")
2030        if file_format:
2031            pattern = self.sql(expression, "pattern")
2032            pattern = f", PATTERN => {pattern}" if pattern else ""
2033            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2034
2035        ordinality = expression.args.get("ordinality") or ""
2036        if ordinality:
2037            ordinality = f" WITH ORDINALITY{alias}"
2038            alias = ""
2039
2040        when = self.sql(expression, "when")
2041        if when:
2042            table = f"{table} {when}"
2043
2044        changes = self.sql(expression, "changes")
2045        changes = f" {changes}" if changes else ""
2046
2047        rows_from = self.expressions(expression, key="rows_from")
2048        if rows_from:
2049            table = f"ROWS FROM {self.wrap(rows_from)}"
2050
2051        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2052
2053    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2054        table = self.func("TABLE", expression.this)
2055        alias = self.sql(expression, "alias")
2056        alias = f" AS {alias}" if alias else ""
2057        sample = self.sql(expression, "sample")
2058        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2059        joins = self.indent(
2060            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2061        )
2062        return f"{table}{alias}{pivots}{sample}{joins}"
2063
2064    def tablesample_sql(
2065        self,
2066        expression: exp.TableSample,
2067        tablesample_keyword: t.Optional[str] = None,
2068    ) -> str:
2069        method = self.sql(expression, "method")
2070        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2071        numerator = self.sql(expression, "bucket_numerator")
2072        denominator = self.sql(expression, "bucket_denominator")
2073        field = self.sql(expression, "bucket_field")
2074        field = f" ON {field}" if field else ""
2075        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2076        seed = self.sql(expression, "seed")
2077        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2078
2079        size = self.sql(expression, "size")
2080        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2081            size = f"{size} ROWS"
2082
2083        percent = self.sql(expression, "percent")
2084        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2085            percent = f"{percent} PERCENT"
2086
2087        expr = f"{bucket}{percent}{size}"
2088        if self.TABLESAMPLE_REQUIRES_PARENS:
2089            expr = f"({expr})"
2090
2091        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2092
2093    def pivot_sql(self, expression: exp.Pivot) -> str:
2094        expressions = self.expressions(expression, flat=True)
2095        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2096
2097        group = self.sql(expression, "group")
2098
2099        if expression.this:
2100            this = self.sql(expression, "this")
2101            if not expressions:
2102                return f"UNPIVOT {this}"
2103
2104            on = f"{self.seg('ON')} {expressions}"
2105            into = self.sql(expression, "into")
2106            into = f"{self.seg('INTO')} {into}" if into else ""
2107            using = self.expressions(expression, key="using", flat=True)
2108            using = f"{self.seg('USING')} {using}" if using else ""
2109            return f"{direction} {this}{on}{into}{using}{group}"
2110
2111        alias = self.sql(expression, "alias")
2112        alias = f" AS {alias}" if alias else ""
2113
2114        fields = self.expressions(
2115            expression,
2116            "fields",
2117            sep=" ",
2118            dynamic=True,
2119            new_line=True,
2120            skip_first=True,
2121            skip_last=True,
2122        )
2123
2124        include_nulls = expression.args.get("include_nulls")
2125        if include_nulls is not None:
2126            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2127        else:
2128            nulls = ""
2129
2130        default_on_null = self.sql(expression, "default_on_null")
2131        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2132        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2133
2134    def version_sql(self, expression: exp.Version) -> str:
2135        this = f"FOR {expression.name}"
2136        kind = expression.text("kind")
2137        expr = self.sql(expression, "expression")
2138        return f"{this} {kind} {expr}"
2139
2140    def tuple_sql(self, expression: exp.Tuple) -> str:
2141        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2142
2143    def update_sql(self, expression: exp.Update) -> str:
2144        this = self.sql(expression, "this")
2145        set_sql = self.expressions(expression, flat=True)
2146        from_sql = self.sql(expression, "from")
2147        where_sql = self.sql(expression, "where")
2148        returning = self.sql(expression, "returning")
2149        order = self.sql(expression, "order")
2150        limit = self.sql(expression, "limit")
2151        if self.RETURNING_END:
2152            expression_sql = f"{from_sql}{where_sql}{returning}"
2153        else:
2154            expression_sql = f"{returning}{from_sql}{where_sql}"
2155        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2156        return self.prepend_ctes(expression, sql)
2157
2158    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2159        values_as_table = values_as_table and self.VALUES_AS_TABLE
2160
2161        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2162        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2163            args = self.expressions(expression)
2164            alias = self.sql(expression, "alias")
2165            values = f"VALUES{self.seg('')}{args}"
2166            values = (
2167                f"({values})"
2168                if self.WRAP_DERIVED_VALUES
2169                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2170                else values
2171            )
2172            return f"{values} AS {alias}" if alias else values
2173
2174        # Converts `VALUES...` expression into a series of select unions.
2175        alias_node = expression.args.get("alias")
2176        column_names = alias_node and alias_node.columns
2177
2178        selects: t.List[exp.Query] = []
2179
2180        for i, tup in enumerate(expression.expressions):
2181            row = tup.expressions
2182
2183            if i == 0 and column_names:
2184                row = [
2185                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2186                ]
2187
2188            selects.append(exp.Select(expressions=row))
2189
2190        if self.pretty:
2191            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2192            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2193            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2194            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2195            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2196
2197        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2198        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2199        return f"({unions}){alias}"
2200
2201    def var_sql(self, expression: exp.Var) -> str:
2202        return self.sql(expression, "this")
2203
2204    @unsupported_args("expressions")
2205    def into_sql(self, expression: exp.Into) -> str:
2206        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2207        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2208        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2209
2210    def from_sql(self, expression: exp.From) -> str:
2211        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2212
2213    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2214        grouping_sets = self.expressions(expression, indent=False)
2215        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2216
2217    def rollup_sql(self, expression: exp.Rollup) -> str:
2218        expressions = self.expressions(expression, indent=False)
2219        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2220
2221    def cube_sql(self, expression: exp.Cube) -> str:
2222        expressions = self.expressions(expression, indent=False)
2223        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2224
2225    def group_sql(self, expression: exp.Group) -> str:
2226        group_by_all = expression.args.get("all")
2227        if group_by_all is True:
2228            modifier = " ALL"
2229        elif group_by_all is False:
2230            modifier = " DISTINCT"
2231        else:
2232            modifier = ""
2233
2234        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2235
2236        grouping_sets = self.expressions(expression, key="grouping_sets")
2237        cube = self.expressions(expression, key="cube")
2238        rollup = self.expressions(expression, key="rollup")
2239
2240        groupings = csv(
2241            self.seg(grouping_sets) if grouping_sets else "",
2242            self.seg(cube) if cube else "",
2243            self.seg(rollup) if rollup else "",
2244            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2245            sep=self.GROUPINGS_SEP,
2246        )
2247
2248        if (
2249            expression.expressions
2250            and groupings
2251            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2252        ):
2253            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2254
2255        return f"{group_by}{groupings}"
2256
2257    def having_sql(self, expression: exp.Having) -> str:
2258        this = self.indent(self.sql(expression, "this"))
2259        return f"{self.seg('HAVING')}{self.sep()}{this}"
2260
2261    def connect_sql(self, expression: exp.Connect) -> str:
2262        start = self.sql(expression, "start")
2263        start = self.seg(f"START WITH {start}") if start else ""
2264        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2265        connect = self.sql(expression, "connect")
2266        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2267        return start + connect
2268
2269    def prior_sql(self, expression: exp.Prior) -> str:
2270        return f"PRIOR {self.sql(expression, 'this')}"
2271
2272    def join_sql(self, expression: exp.Join) -> str:
2273        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2274            side = None
2275        else:
2276            side = expression.side
2277
2278        op_sql = " ".join(
2279            op
2280            for op in (
2281                expression.method,
2282                "GLOBAL" if expression.args.get("global") else None,
2283                side,
2284                expression.kind,
2285                expression.hint if self.JOIN_HINTS else None,
2286            )
2287            if op
2288        )
2289        match_cond = self.sql(expression, "match_condition")
2290        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2291        on_sql = self.sql(expression, "on")
2292        using = expression.args.get("using")
2293
2294        if not on_sql and using:
2295            on_sql = csv(*(self.sql(column) for column in using))
2296
2297        this = expression.this
2298        this_sql = self.sql(this)
2299
2300        exprs = self.expressions(expression)
2301        if exprs:
2302            this_sql = f"{this_sql},{self.seg(exprs)}"
2303
2304        if on_sql:
2305            on_sql = self.indent(on_sql, skip_first=True)
2306            space = self.seg(" " * self.pad) if self.pretty else " "
2307            if using:
2308                on_sql = f"{space}USING ({on_sql})"
2309            else:
2310                on_sql = f"{space}ON {on_sql}"
2311        elif not op_sql:
2312            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2313                return f" {this_sql}"
2314
2315            return f", {this_sql}"
2316
2317        if op_sql != "STRAIGHT_JOIN":
2318            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2319
2320        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2321        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2322
2323    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2324        args = self.expressions(expression, flat=True)
2325        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2326        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2327
2328    def lateral_op(self, expression: exp.Lateral) -> str:
2329        cross_apply = expression.args.get("cross_apply")
2330
2331        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2332        if cross_apply is True:
2333            op = "INNER JOIN "
2334        elif cross_apply is False:
2335            op = "LEFT JOIN "
2336        else:
2337            op = ""
2338
2339        return f"{op}LATERAL"
2340
2341    def lateral_sql(self, expression: exp.Lateral) -> str:
2342        this = self.sql(expression, "this")
2343
2344        if expression.args.get("view"):
2345            alias = expression.args["alias"]
2346            columns = self.expressions(alias, key="columns", flat=True)
2347            table = f" {alias.name}" if alias.name else ""
2348            columns = f" AS {columns}" if columns else ""
2349            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2350            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2351
2352        alias = self.sql(expression, "alias")
2353        alias = f" AS {alias}" if alias else ""
2354
2355        ordinality = expression.args.get("ordinality") or ""
2356        if ordinality:
2357            ordinality = f" WITH ORDINALITY{alias}"
2358            alias = ""
2359
2360        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2361
2362    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2363        this = self.sql(expression, "this")
2364
2365        args = [
2366            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2367            for e in (expression.args.get(k) for k in ("offset", "expression"))
2368            if e
2369        ]
2370
2371        args_sql = ", ".join(self.sql(e) for e in args)
2372        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2373        expressions = self.expressions(expression, flat=True)
2374        limit_options = self.sql(expression, "limit_options")
2375        expressions = f" BY {expressions}" if expressions else ""
2376
2377        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2378
2379    def offset_sql(self, expression: exp.Offset) -> str:
2380        this = self.sql(expression, "this")
2381        value = expression.expression
2382        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2383        expressions = self.expressions(expression, flat=True)
2384        expressions = f" BY {expressions}" if expressions else ""
2385        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2386
2387    def setitem_sql(self, expression: exp.SetItem) -> str:
2388        kind = self.sql(expression, "kind")
2389        kind = f"{kind} " if kind else ""
2390        this = self.sql(expression, "this")
2391        expressions = self.expressions(expression)
2392        collate = self.sql(expression, "collate")
2393        collate = f" COLLATE {collate}" if collate else ""
2394        global_ = "GLOBAL " if expression.args.get("global") else ""
2395        return f"{global_}{kind}{this}{expressions}{collate}"
2396
2397    def set_sql(self, expression: exp.Set) -> str:
2398        expressions = f" {self.expressions(expression, flat=True)}"
2399        tag = " TAG" if expression.args.get("tag") else ""
2400        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2401
2402    def pragma_sql(self, expression: exp.Pragma) -> str:
2403        return f"PRAGMA {self.sql(expression, 'this')}"
2404
2405    def lock_sql(self, expression: exp.Lock) -> str:
2406        if not self.LOCKING_READS_SUPPORTED:
2407            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2408            return ""
2409
2410        update = expression.args["update"]
2411        key = expression.args.get("key")
2412        if update:
2413            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2414        else:
2415            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2416        expressions = self.expressions(expression, flat=True)
2417        expressions = f" OF {expressions}" if expressions else ""
2418        wait = expression.args.get("wait")
2419
2420        if wait is not None:
2421            if isinstance(wait, exp.Literal):
2422                wait = f" WAIT {self.sql(wait)}"
2423            else:
2424                wait = " NOWAIT" if wait else " SKIP LOCKED"
2425
2426        return f"{lock_type}{expressions}{wait or ''}"
2427
2428    def literal_sql(self, expression: exp.Literal) -> str:
2429        text = expression.this or ""
2430        if expression.is_string:
2431            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2432        return text
2433
2434    def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2435        if self.dialect.ESCAPED_SEQUENCES:
2436            to_escaped = self.dialect.ESCAPED_SEQUENCES
2437            text = "".join(
2438                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2439            )
2440
2441        return self._replace_line_breaks(text).replace(
2442            self.dialect.QUOTE_END, self._escaped_quote_end
2443        )
2444
2445    def loaddata_sql(self, expression: exp.LoadData) -> str:
2446        local = " LOCAL" if expression.args.get("local") else ""
2447        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2448        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2449        this = f" INTO TABLE {self.sql(expression, 'this')}"
2450        partition = self.sql(expression, "partition")
2451        partition = f" {partition}" if partition else ""
2452        input_format = self.sql(expression, "input_format")
2453        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2454        serde = self.sql(expression, "serde")
2455        serde = f" SERDE {serde}" if serde else ""
2456        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2457
2458    def null_sql(self, *_) -> str:
2459        return "NULL"
2460
2461    def boolean_sql(self, expression: exp.Boolean) -> str:
2462        return "TRUE" if expression.this else "FALSE"
2463
2464    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2465        this = self.sql(expression, "this")
2466        this = f"{this} " if this else this
2467        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2468        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2469
2470    def withfill_sql(self, expression: exp.WithFill) -> str:
2471        from_sql = self.sql(expression, "from")
2472        from_sql = f" FROM {from_sql}" if from_sql else ""
2473        to_sql = self.sql(expression, "to")
2474        to_sql = f" TO {to_sql}" if to_sql else ""
2475        step_sql = self.sql(expression, "step")
2476        step_sql = f" STEP {step_sql}" if step_sql else ""
2477        interpolated_values = [
2478            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2479            if isinstance(e, exp.Alias)
2480            else self.sql(e, "this")
2481            for e in expression.args.get("interpolate") or []
2482        ]
2483        interpolate = (
2484            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2485        )
2486        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2487
2488    def cluster_sql(self, expression: exp.Cluster) -> str:
2489        return self.op_expressions("CLUSTER BY", expression)
2490
2491    def distribute_sql(self, expression: exp.Distribute) -> str:
2492        return self.op_expressions("DISTRIBUTE BY", expression)
2493
2494    def sort_sql(self, expression: exp.Sort) -> str:
2495        return self.op_expressions("SORT BY", expression)
2496
2497    def ordered_sql(self, expression: exp.Ordered) -> str:
2498        desc = expression.args.get("desc")
2499        asc = not desc
2500
2501        nulls_first = expression.args.get("nulls_first")
2502        nulls_last = not nulls_first
2503        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2504        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2505        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2506
2507        this = self.sql(expression, "this")
2508
2509        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2510        nulls_sort_change = ""
2511        if nulls_first and (
2512            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2513        ):
2514            nulls_sort_change = " NULLS FIRST"
2515        elif (
2516            nulls_last
2517            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2518            and not nulls_are_last
2519        ):
2520            nulls_sort_change = " NULLS LAST"
2521
2522        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2523        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2524            window = expression.find_ancestor(exp.Window, exp.Select)
2525            if isinstance(window, exp.Window) and window.args.get("spec"):
2526                self.unsupported(
2527                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2528                )
2529                nulls_sort_change = ""
2530            elif self.NULL_ORDERING_SUPPORTED is False and (
2531                (asc and nulls_sort_change == " NULLS LAST")
2532                or (desc and nulls_sort_change == " NULLS FIRST")
2533            ):
2534                # BigQuery does not allow these ordering/nulls combinations when used under
2535                # an aggregation func or under a window containing one
2536                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2537
2538                if isinstance(ancestor, exp.Window):
2539                    ancestor = ancestor.this
2540                if isinstance(ancestor, exp.AggFunc):
2541                    self.unsupported(
2542                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2543                    )
2544                    nulls_sort_change = ""
2545            elif self.NULL_ORDERING_SUPPORTED is None:
2546                if expression.this.is_int:
2547                    self.unsupported(
2548                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2549                    )
2550                elif not isinstance(expression.this, exp.Rand):
2551                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2552                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2553                nulls_sort_change = ""
2554
2555        with_fill = self.sql(expression, "with_fill")
2556        with_fill = f" {with_fill}" if with_fill else ""
2557
2558        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2559
2560    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2561        window_frame = self.sql(expression, "window_frame")
2562        window_frame = f"{window_frame} " if window_frame else ""
2563
2564        this = self.sql(expression, "this")
2565
2566        return f"{window_frame}{this}"
2567
2568    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2569        partition = self.partition_by_sql(expression)
2570        order = self.sql(expression, "order")
2571        measures = self.expressions(expression, key="measures")
2572        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2573        rows = self.sql(expression, "rows")
2574        rows = self.seg(rows) if rows else ""
2575        after = self.sql(expression, "after")
2576        after = self.seg(after) if after else ""
2577        pattern = self.sql(expression, "pattern")
2578        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2579        definition_sqls = [
2580            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2581            for definition in expression.args.get("define", [])
2582        ]
2583        definitions = self.expressions(sqls=definition_sqls)
2584        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2585        body = "".join(
2586            (
2587                partition,
2588                order,
2589                measures,
2590                rows,
2591                after,
2592                pattern,
2593                define,
2594            )
2595        )
2596        alias = self.sql(expression, "alias")
2597        alias = f" {alias}" if alias else ""
2598        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2599
2600    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2601        limit = expression.args.get("limit")
2602
2603        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2604            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2605        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2606            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2607
2608        return csv(
2609            *sqls,
2610            *[self.sql(join) for join in expression.args.get("joins") or []],
2611            self.sql(expression, "match"),
2612            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2613            self.sql(expression, "prewhere"),
2614            self.sql(expression, "where"),
2615            self.sql(expression, "connect"),
2616            self.sql(expression, "group"),
2617            self.sql(expression, "having"),
2618            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2619            self.sql(expression, "order"),
2620            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2621            *self.after_limit_modifiers(expression),
2622            self.options_modifier(expression),
2623            self.for_modifiers(expression),
2624            sep="",
2625        )
2626
2627    def options_modifier(self, expression: exp.Expression) -> str:
2628        options = self.expressions(expression, key="options")
2629        return f" {options}" if options else ""
2630
2631    def for_modifiers(self, expression: exp.Expression) -> str:
2632        for_modifiers = self.expressions(expression, key="for")
2633        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2634
2635    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2636        self.unsupported("Unsupported query option.")
2637        return ""
2638
2639    def offset_limit_modifiers(
2640        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2641    ) -> t.List[str]:
2642        return [
2643            self.sql(expression, "offset") if fetch else self.sql(limit),
2644            self.sql(limit) if fetch else self.sql(expression, "offset"),
2645        ]
2646
2647    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2648        locks = self.expressions(expression, key="locks", sep=" ")
2649        locks = f" {locks}" if locks else ""
2650        return [locks, self.sql(expression, "sample")]
2651
2652    def select_sql(self, expression: exp.Select) -> str:
2653        into = expression.args.get("into")
2654        if not self.SUPPORTS_SELECT_INTO and into:
2655            into.pop()
2656
2657        hint = self.sql(expression, "hint")
2658        distinct = self.sql(expression, "distinct")
2659        distinct = f" {distinct}" if distinct else ""
2660        kind = self.sql(expression, "kind")
2661
2662        limit = expression.args.get("limit")
2663        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2664            top = self.limit_sql(limit, top=True)
2665            limit.pop()
2666        else:
2667            top = ""
2668
2669        expressions = self.expressions(expression)
2670
2671        if kind:
2672            if kind in self.SELECT_KINDS:
2673                kind = f" AS {kind}"
2674            else:
2675                if kind == "STRUCT":
2676                    expressions = self.expressions(
2677                        sqls=[
2678                            self.sql(
2679                                exp.Struct(
2680                                    expressions=[
2681                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2682                                        if isinstance(e, exp.Alias)
2683                                        else e
2684                                        for e in expression.expressions
2685                                    ]
2686                                )
2687                            )
2688                        ]
2689                    )
2690                kind = ""
2691
2692        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2693        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2694
2695        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2696        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2697        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2698        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2699        sql = self.query_modifiers(
2700            expression,
2701            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2702            self.sql(expression, "into", comment=False),
2703            self.sql(expression, "from", comment=False),
2704        )
2705
2706        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2707        if expression.args.get("with"):
2708            sql = self.maybe_comment(sql, expression)
2709            expression.pop_comments()
2710
2711        sql = self.prepend_ctes(expression, sql)
2712
2713        if not self.SUPPORTS_SELECT_INTO and into:
2714            if into.args.get("temporary"):
2715                table_kind = " TEMPORARY"
2716            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2717                table_kind = " UNLOGGED"
2718            else:
2719                table_kind = ""
2720            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2721
2722        return sql
2723
2724    def schema_sql(self, expression: exp.Schema) -> str:
2725        this = self.sql(expression, "this")
2726        sql = self.schema_columns_sql(expression)
2727        return f"{this} {sql}" if this and sql else this or sql
2728
2729    def schema_columns_sql(self, expression: exp.Schema) -> str:
2730        if expression.expressions:
2731            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2732        return ""
2733
2734    def star_sql(self, expression: exp.Star) -> str:
2735        except_ = self.expressions(expression, key="except", flat=True)
2736        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2737        replace = self.expressions(expression, key="replace", flat=True)
2738        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2739        rename = self.expressions(expression, key="rename", flat=True)
2740        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2741        return f"*{except_}{replace}{rename}"
2742
2743    def parameter_sql(self, expression: exp.Parameter) -> str:
2744        this = self.sql(expression, "this")
2745        return f"{self.PARAMETER_TOKEN}{this}"
2746
2747    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2748        this = self.sql(expression, "this")
2749        kind = expression.text("kind")
2750        if kind:
2751            kind = f"{kind}."
2752        return f"@@{kind}{this}"
2753
2754    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2755        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2756
2757    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2758        alias = self.sql(expression, "alias")
2759        alias = f"{sep}{alias}" if alias else ""
2760        sample = self.sql(expression, "sample")
2761        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2762            alias = f"{sample}{alias}"
2763
2764            # Set to None so it's not generated again by self.query_modifiers()
2765            expression.set("sample", None)
2766
2767        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2768        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2769        return self.prepend_ctes(expression, sql)
2770
2771    def qualify_sql(self, expression: exp.Qualify) -> str:
2772        this = self.indent(self.sql(expression, "this"))
2773        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2774
2775    def unnest_sql(self, expression: exp.Unnest) -> str:
2776        args = self.expressions(expression, flat=True)
2777
2778        alias = expression.args.get("alias")
2779        offset = expression.args.get("offset")
2780
2781        if self.UNNEST_WITH_ORDINALITY:
2782            if alias and isinstance(offset, exp.Expression):
2783                alias.append("columns", offset)
2784
2785        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2786            columns = alias.columns
2787            alias = self.sql(columns[0]) if columns else ""
2788        else:
2789            alias = self.sql(alias)
2790
2791        alias = f" AS {alias}" if alias else alias
2792        if self.UNNEST_WITH_ORDINALITY:
2793            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2794        else:
2795            if isinstance(offset, exp.Expression):
2796                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2797            elif offset:
2798                suffix = f"{alias} WITH OFFSET"
2799            else:
2800                suffix = alias
2801
2802        return f"UNNEST({args}){suffix}"
2803
2804    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2805        return ""
2806
2807    def where_sql(self, expression: exp.Where) -> str:
2808        this = self.indent(self.sql(expression, "this"))
2809        return f"{self.seg('WHERE')}{self.sep()}{this}"
2810
2811    def window_sql(self, expression: exp.Window) -> str:
2812        this = self.sql(expression, "this")
2813        partition = self.partition_by_sql(expression)
2814        order = expression.args.get("order")
2815        order = self.order_sql(order, flat=True) if order else ""
2816        spec = self.sql(expression, "spec")
2817        alias = self.sql(expression, "alias")
2818        over = self.sql(expression, "over") or "OVER"
2819
2820        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2821
2822        first = expression.args.get("first")
2823        if first is None:
2824            first = ""
2825        else:
2826            first = "FIRST" if first else "LAST"
2827
2828        if not partition and not order and not spec and alias:
2829            return f"{this} {alias}"
2830
2831        args = self.format_args(
2832            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2833        )
2834        return f"{this} ({args})"
2835
2836    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2837        partition = self.expressions(expression, key="partition_by", flat=True)
2838        return f"PARTITION BY {partition}" if partition else ""
2839
2840    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2841        kind = self.sql(expression, "kind")
2842        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2843        end = (
2844            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2845            or "CURRENT ROW"
2846        )
2847
2848        window_spec = f"{kind} BETWEEN {start} AND {end}"
2849
2850        exclude = self.sql(expression, "exclude")
2851        if exclude:
2852            if self.SUPPORTS_WINDOW_EXCLUDE:
2853                window_spec += f" EXCLUDE {exclude}"
2854            else:
2855                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2856
2857        return window_spec
2858
2859    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2860        this = self.sql(expression, "this")
2861        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2862        return f"{this} WITHIN GROUP ({expression_sql})"
2863
2864    def between_sql(self, expression: exp.Between) -> str:
2865        this = self.sql(expression, "this")
2866        low = self.sql(expression, "low")
2867        high = self.sql(expression, "high")
2868        return f"{this} BETWEEN {low} AND {high}"
2869
2870    def bracket_offset_expressions(
2871        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2872    ) -> t.List[exp.Expression]:
2873        return apply_index_offset(
2874            expression.this,
2875            expression.expressions,
2876            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2877            dialect=self.dialect,
2878        )
2879
2880    def bracket_sql(self, expression: exp.Bracket) -> str:
2881        expressions = self.bracket_offset_expressions(expression)
2882        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2883        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2884
2885    def all_sql(self, expression: exp.All) -> str:
2886        return f"ALL {self.wrap(expression)}"
2887
2888    def any_sql(self, expression: exp.Any) -> str:
2889        this = self.sql(expression, "this")
2890        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2891            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2892                this = self.wrap(this)
2893            return f"ANY{this}"
2894        return f"ANY {this}"
2895
2896    def exists_sql(self, expression: exp.Exists) -> str:
2897        return f"EXISTS{self.wrap(expression)}"
2898
2899    def case_sql(self, expression: exp.Case) -> str:
2900        this = self.sql(expression, "this")
2901        statements = [f"CASE {this}" if this else "CASE"]
2902
2903        for e in expression.args["ifs"]:
2904            statements.append(f"WHEN {self.sql(e, 'this')}")
2905            statements.append(f"THEN {self.sql(e, 'true')}")
2906
2907        default = self.sql(expression, "default")
2908
2909        if default:
2910            statements.append(f"ELSE {default}")
2911
2912        statements.append("END")
2913
2914        if self.pretty and self.too_wide(statements):
2915            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2916
2917        return " ".join(statements)
2918
2919    def constraint_sql(self, expression: exp.Constraint) -> str:
2920        this = self.sql(expression, "this")
2921        expressions = self.expressions(expression, flat=True)
2922        return f"CONSTRAINT {this} {expressions}"
2923
2924    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2925        order = expression.args.get("order")
2926        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2927        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
2928
2929    def extract_sql(self, expression: exp.Extract) -> str:
2930        from sqlglot.dialects.dialect import map_date_part
2931
2932        this = (
2933            map_date_part(expression.this, self.dialect)
2934            if self.NORMALIZE_EXTRACT_DATE_PARTS
2935            else expression.this
2936        )
2937        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
2938        expression_sql = self.sql(expression, "expression")
2939
2940        return f"EXTRACT({this_sql} FROM {expression_sql})"
2941
2942    def trim_sql(self, expression: exp.Trim) -> str:
2943        trim_type = self.sql(expression, "position")
2944
2945        if trim_type == "LEADING":
2946            func_name = "LTRIM"
2947        elif trim_type == "TRAILING":
2948            func_name = "RTRIM"
2949        else:
2950            func_name = "TRIM"
2951
2952        return self.func(func_name, expression.this, expression.expression)
2953
2954    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
2955        args = expression.expressions
2956        if isinstance(expression, exp.ConcatWs):
2957            args = args[1:]  # Skip the delimiter
2958
2959        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
2960            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2961
2962        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2963            args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
2964
2965        return args
2966
2967    def concat_sql(self, expression: exp.Concat) -> str:
2968        expressions = self.convert_concat_args(expression)
2969
2970        # Some dialects don't allow a single-argument CONCAT call
2971        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
2972            return self.sql(expressions[0])
2973
2974        return self.func("CONCAT", *expressions)
2975
2976    def concatws_sql(self, expression: exp.ConcatWs) -> str:
2977        return self.func(
2978            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
2979        )
2980
2981    def check_sql(self, expression: exp.Check) -> str:
2982        this = self.sql(expression, key="this")
2983        return f"CHECK ({this})"
2984
2985    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
2986        expressions = self.expressions(expression, flat=True)
2987        expressions = f" ({expressions})" if expressions else ""
2988        reference = self.sql(expression, "reference")
2989        reference = f" {reference}" if reference else ""
2990        delete = self.sql(expression, "delete")
2991        delete = f" ON DELETE {delete}" if delete else ""
2992        update = self.sql(expression, "update")
2993        update = f" ON UPDATE {update}" if update else ""
2994        options = self.expressions(expression, key="options", flat=True, sep=" ")
2995        options = f" {options}" if options else ""
2996        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
2997
2998    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
2999        expressions = self.expressions(expression, flat=True)
3000        options = self.expressions(expression, key="options", flat=True, sep=" ")
3001        options = f" {options}" if options else ""
3002        return f"PRIMARY KEY ({expressions}){options}"
3003
3004    def if_sql(self, expression: exp.If) -> str:
3005        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3006
3007    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3008        modifier = expression.args.get("modifier")
3009        modifier = f" {modifier}" if modifier else ""
3010        return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3011
3012    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3013        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3014
3015    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3016        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3017
3018        if expression.args.get("escape"):
3019            path = self.escape_str(path)
3020
3021        if self.QUOTE_JSON_PATH:
3022            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3023
3024        return path
3025
3026    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3027        if isinstance(expression, exp.JSONPathPart):
3028            transform = self.TRANSFORMS.get(expression.__class__)
3029            if not callable(transform):
3030                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3031                return ""
3032
3033            return transform(self, expression)
3034
3035        if isinstance(expression, int):
3036            return str(expression)
3037
3038        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3039            escaped = expression.replace("'", "\\'")
3040            escaped = f"\\'{expression}\\'"
3041        else:
3042            escaped = expression.replace('"', '\\"')
3043            escaped = f'"{escaped}"'
3044
3045        return escaped
3046
3047    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3048        return f"{self.sql(expression, 'this')} FORMAT JSON"
3049
3050    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3051        # Output the Teradata column FORMAT override.
3052        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3053        this = self.sql(expression, "this")
3054        fmt = self.sql(expression, "format")
3055        return f"{this} (FORMAT {fmt})"
3056
3057    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3058        null_handling = expression.args.get("null_handling")
3059        null_handling = f" {null_handling}" if null_handling else ""
3060
3061        unique_keys = expression.args.get("unique_keys")
3062        if unique_keys is not None:
3063            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3064        else:
3065            unique_keys = ""
3066
3067        return_type = self.sql(expression, "return_type")
3068        return_type = f" RETURNING {return_type}" if return_type else ""
3069        encoding = self.sql(expression, "encoding")
3070        encoding = f" ENCODING {encoding}" if encoding else ""
3071
3072        return self.func(
3073            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3074            *expression.expressions,
3075            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3076        )
3077
3078    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3079        return self.jsonobject_sql(expression)
3080
3081    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3082        null_handling = expression.args.get("null_handling")
3083        null_handling = f" {null_handling}" if null_handling else ""
3084        return_type = self.sql(expression, "return_type")
3085        return_type = f" RETURNING {return_type}" if return_type else ""
3086        strict = " STRICT" if expression.args.get("strict") else ""
3087        return self.func(
3088            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3089        )
3090
3091    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3092        this = self.sql(expression, "this")
3093        order = self.sql(expression, "order")
3094        null_handling = expression.args.get("null_handling")
3095        null_handling = f" {null_handling}" if null_handling else ""
3096        return_type = self.sql(expression, "return_type")
3097        return_type = f" RETURNING {return_type}" if return_type else ""
3098        strict = " STRICT" if expression.args.get("strict") else ""
3099        return self.func(
3100            "JSON_ARRAYAGG",
3101            this,
3102            suffix=f"{order}{null_handling}{return_type}{strict})",
3103        )
3104
3105    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3106        path = self.sql(expression, "path")
3107        path = f" PATH {path}" if path else ""
3108        nested_schema = self.sql(expression, "nested_schema")
3109
3110        if nested_schema:
3111            return f"NESTED{path} {nested_schema}"
3112
3113        this = self.sql(expression, "this")
3114        kind = self.sql(expression, "kind")
3115        kind = f" {kind}" if kind else ""
3116        return f"{this}{kind}{path}"
3117
3118    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3119        return self.func("COLUMNS", *expression.expressions)
3120
3121    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3122        this = self.sql(expression, "this")
3123        path = self.sql(expression, "path")
3124        path = f", {path}" if path else ""
3125        error_handling = expression.args.get("error_handling")
3126        error_handling = f" {error_handling}" if error_handling else ""
3127        empty_handling = expression.args.get("empty_handling")
3128        empty_handling = f" {empty_handling}" if empty_handling else ""
3129        schema = self.sql(expression, "schema")
3130        return self.func(
3131            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3132        )
3133
3134    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3135        this = self.sql(expression, "this")
3136        kind = self.sql(expression, "kind")
3137        path = self.sql(expression, "path")
3138        path = f" {path}" if path else ""
3139        as_json = " AS JSON" if expression.args.get("as_json") else ""
3140        return f"{this} {kind}{path}{as_json}"
3141
3142    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3143        this = self.sql(expression, "this")
3144        path = self.sql(expression, "path")
3145        path = f", {path}" if path else ""
3146        expressions = self.expressions(expression)
3147        with_ = (
3148            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3149            if expressions
3150            else ""
3151        )
3152        return f"OPENJSON({this}{path}){with_}"
3153
3154    def in_sql(self, expression: exp.In) -> str:
3155        query = expression.args.get("query")
3156        unnest = expression.args.get("unnest")
3157        field = expression.args.get("field")
3158        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3159
3160        if query:
3161            in_sql = self.sql(query)
3162        elif unnest:
3163            in_sql = self.in_unnest_op(unnest)
3164        elif field:
3165            in_sql = self.sql(field)
3166        else:
3167            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3168
3169        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3170
3171    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3172        return f"(SELECT {self.sql(unnest)})"
3173
3174    def interval_sql(self, expression: exp.Interval) -> str:
3175        unit = self.sql(expression, "unit")
3176        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3177            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3178        unit = f" {unit}" if unit else ""
3179
3180        if self.SINGLE_STRING_INTERVAL:
3181            this = expression.this.name if expression.this else ""
3182            return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3183
3184        this = self.sql(expression, "this")
3185        if this:
3186            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3187            this = f" {this}" if unwrapped else f" ({this})"
3188
3189        return f"INTERVAL{this}{unit}"
3190
3191    def return_sql(self, expression: exp.Return) -> str:
3192        return f"RETURN {self.sql(expression, 'this')}"
3193
3194    def reference_sql(self, expression: exp.Reference) -> str:
3195        this = self.sql(expression, "this")
3196        expressions = self.expressions(expression, flat=True)
3197        expressions = f"({expressions})" if expressions else ""
3198        options = self.expressions(expression, key="options", flat=True, sep=" ")
3199        options = f" {options}" if options else ""
3200        return f"REFERENCES {this}{expressions}{options}"
3201
3202    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3203        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3204        parent = expression.parent
3205        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3206        return self.func(
3207            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3208        )
3209
3210    def paren_sql(self, expression: exp.Paren) -> str:
3211        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3212        return f"({sql}{self.seg(')', sep='')}"
3213
3214    def neg_sql(self, expression: exp.Neg) -> str:
3215        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3216        this_sql = self.sql(expression, "this")
3217        sep = " " if this_sql[0] == "-" else ""
3218        return f"-{sep}{this_sql}"
3219
3220    def not_sql(self, expression: exp.Not) -> str:
3221        return f"NOT {self.sql(expression, 'this')}"
3222
3223    def alias_sql(self, expression: exp.Alias) -> str:
3224        alias = self.sql(expression, "alias")
3225        alias = f" AS {alias}" if alias else ""
3226        return f"{self.sql(expression, 'this')}{alias}"
3227
3228    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3229        alias = expression.args["alias"]
3230
3231        parent = expression.parent
3232        pivot = parent and parent.parent
3233
3234        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3235            identifier_alias = isinstance(alias, exp.Identifier)
3236            literal_alias = isinstance(alias, exp.Literal)
3237
3238            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3239                alias.replace(exp.Literal.string(alias.output_name))
3240            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3241                alias.replace(exp.to_identifier(alias.output_name))
3242
3243        return self.alias_sql(expression)
3244
3245    def aliases_sql(self, expression: exp.Aliases) -> str:
3246        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3247
3248    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3249        this = self.sql(expression, "this")
3250        index = self.sql(expression, "expression")
3251        return f"{this} AT {index}"
3252
3253    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3254        this = self.sql(expression, "this")
3255        zone = self.sql(expression, "zone")
3256        return f"{this} AT TIME ZONE {zone}"
3257
3258    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3259        this = self.sql(expression, "this")
3260        zone = self.sql(expression, "zone")
3261        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3262
3263    def add_sql(self, expression: exp.Add) -> str:
3264        return self.binary(expression, "+")
3265
3266    def and_sql(
3267        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3268    ) -> str:
3269        return self.connector_sql(expression, "AND", stack)
3270
3271    def or_sql(
3272        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3273    ) -> str:
3274        return self.connector_sql(expression, "OR", stack)
3275
3276    def xor_sql(
3277        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3278    ) -> str:
3279        return self.connector_sql(expression, "XOR", stack)
3280
3281    def connector_sql(
3282        self,
3283        expression: exp.Connector,
3284        op: str,
3285        stack: t.Optional[t.List[str | exp.Expression]] = None,
3286    ) -> str:
3287        if stack is not None:
3288            if expression.expressions:
3289                stack.append(self.expressions(expression, sep=f" {op} "))
3290            else:
3291                stack.append(expression.right)
3292                if expression.comments and self.comments:
3293                    for comment in expression.comments:
3294                        if comment:
3295                            op += f" /*{self.sanitize_comment(comment)}*/"
3296                stack.extend((op, expression.left))
3297            return op
3298
3299        stack = [expression]
3300        sqls: t.List[str] = []
3301        ops = set()
3302
3303        while stack:
3304            node = stack.pop()
3305            if isinstance(node, exp.Connector):
3306                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3307            else:
3308                sql = self.sql(node)
3309                if sqls and sqls[-1] in ops:
3310                    sqls[-1] += f" {sql}"
3311                else:
3312                    sqls.append(sql)
3313
3314        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3315        return sep.join(sqls)
3316
3317    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3318        return self.binary(expression, "&")
3319
3320    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3321        return self.binary(expression, "<<")
3322
3323    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3324        return f"~{self.sql(expression, 'this')}"
3325
3326    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3327        return self.binary(expression, "|")
3328
3329    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3330        return self.binary(expression, ">>")
3331
3332    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3333        return self.binary(expression, "^")
3334
3335    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3336        format_sql = self.sql(expression, "format")
3337        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3338        to_sql = self.sql(expression, "to")
3339        to_sql = f" {to_sql}" if to_sql else ""
3340        action = self.sql(expression, "action")
3341        action = f" {action}" if action else ""
3342        default = self.sql(expression, "default")
3343        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3344        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3345
3346    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3347        zone = self.sql(expression, "this")
3348        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3349
3350    def collate_sql(self, expression: exp.Collate) -> str:
3351        if self.COLLATE_IS_FUNC:
3352            return self.function_fallback_sql(expression)
3353        return self.binary(expression, "COLLATE")
3354
3355    def command_sql(self, expression: exp.Command) -> str:
3356        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3357
3358    def comment_sql(self, expression: exp.Comment) -> str:
3359        this = self.sql(expression, "this")
3360        kind = expression.args["kind"]
3361        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3362        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3363        expression_sql = self.sql(expression, "expression")
3364        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3365
3366    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3367        this = self.sql(expression, "this")
3368        delete = " DELETE" if expression.args.get("delete") else ""
3369        recompress = self.sql(expression, "recompress")
3370        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3371        to_disk = self.sql(expression, "to_disk")
3372        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3373        to_volume = self.sql(expression, "to_volume")
3374        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3375        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3376
3377    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3378        where = self.sql(expression, "where")
3379        group = self.sql(expression, "group")
3380        aggregates = self.expressions(expression, key="aggregates")
3381        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3382
3383        if not (where or group or aggregates) and len(expression.expressions) == 1:
3384            return f"TTL {self.expressions(expression, flat=True)}"
3385
3386        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3387
3388    def transaction_sql(self, expression: exp.Transaction) -> str:
3389        return "BEGIN"
3390
3391    def commit_sql(self, expression: exp.Commit) -> str:
3392        chain = expression.args.get("chain")
3393        if chain is not None:
3394            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3395
3396        return f"COMMIT{chain or ''}"
3397
3398    def rollback_sql(self, expression: exp.Rollback) -> str:
3399        savepoint = expression.args.get("savepoint")
3400        savepoint = f" TO {savepoint}" if savepoint else ""
3401        return f"ROLLBACK{savepoint}"
3402
3403    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3404        this = self.sql(expression, "this")
3405
3406        dtype = self.sql(expression, "dtype")
3407        if dtype:
3408            collate = self.sql(expression, "collate")
3409            collate = f" COLLATE {collate}" if collate else ""
3410            using = self.sql(expression, "using")
3411            using = f" USING {using}" if using else ""
3412            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3413            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3414
3415        default = self.sql(expression, "default")
3416        if default:
3417            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3418
3419        comment = self.sql(expression, "comment")
3420        if comment:
3421            return f"ALTER COLUMN {this} COMMENT {comment}"
3422
3423        visible = expression.args.get("visible")
3424        if visible:
3425            return f"ALTER COLUMN {this} SET {visible}"
3426
3427        allow_null = expression.args.get("allow_null")
3428        drop = expression.args.get("drop")
3429
3430        if not drop and not allow_null:
3431            self.unsupported("Unsupported ALTER COLUMN syntax")
3432
3433        if allow_null is not None:
3434            keyword = "DROP" if drop else "SET"
3435            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3436
3437        return f"ALTER COLUMN {this} DROP DEFAULT"
3438
3439    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3440        this = self.sql(expression, "this")
3441
3442        visible = expression.args.get("visible")
3443        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3444
3445        return f"ALTER INDEX {this} {visible_sql}"
3446
3447    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3448        this = self.sql(expression, "this")
3449        if not isinstance(expression.this, exp.Var):
3450            this = f"KEY DISTKEY {this}"
3451        return f"ALTER DISTSTYLE {this}"
3452
3453    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3454        compound = " COMPOUND" if expression.args.get("compound") else ""
3455        this = self.sql(expression, "this")
3456        expressions = self.expressions(expression, flat=True)
3457        expressions = f"({expressions})" if expressions else ""
3458        return f"ALTER{compound} SORTKEY {this or expressions}"
3459
3460    def alterrename_sql(self, expression: exp.AlterRename) -> str:
3461        if not self.RENAME_TABLE_WITH_DB:
3462            # Remove db from tables
3463            expression = expression.transform(
3464                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3465            ).assert_is(exp.AlterRename)
3466        this = self.sql(expression, "this")
3467        return f"RENAME TO {this}"
3468
3469    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3470        exists = " IF EXISTS" if expression.args.get("exists") else ""
3471        old_column = self.sql(expression, "this")
3472        new_column = self.sql(expression, "to")
3473        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3474
3475    def alterset_sql(self, expression: exp.AlterSet) -> str:
3476        exprs = self.expressions(expression, flat=True)
3477        if self.ALTER_SET_WRAPPED:
3478            exprs = f"({exprs})"
3479
3480        return f"SET {exprs}"
3481
3482    def alter_sql(self, expression: exp.Alter) -> str:
3483        actions = expression.args["actions"]
3484
3485        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3486            actions[0], exp.ColumnDef
3487        ):
3488            actions_sql = self.expressions(expression, key="actions", flat=True)
3489            actions_sql = f"ADD {actions_sql}"
3490        else:
3491            actions_list = []
3492            for action in actions:
3493                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3494                    action_sql = self.add_column_sql(action)
3495                else:
3496                    action_sql = self.sql(action)
3497                    if isinstance(action, exp.Query):
3498                        action_sql = f"AS {action_sql}"
3499
3500                actions_list.append(action_sql)
3501
3502            actions_sql = self.format_args(*actions_list).lstrip("\n")
3503
3504        exists = " IF EXISTS" if expression.args.get("exists") else ""
3505        on_cluster = self.sql(expression, "cluster")
3506        on_cluster = f" {on_cluster}" if on_cluster else ""
3507        only = " ONLY" if expression.args.get("only") else ""
3508        options = self.expressions(expression, key="options")
3509        options = f", {options}" if options else ""
3510        kind = self.sql(expression, "kind")
3511        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3512
3513        return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3514
3515    def add_column_sql(self, expression: exp.Expression) -> str:
3516        sql = self.sql(expression)
3517        if isinstance(expression, exp.Schema):
3518            column_text = " COLUMNS"
3519        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3520            column_text = " COLUMN"
3521        else:
3522            column_text = ""
3523
3524        return f"ADD{column_text} {sql}"
3525
3526    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3527        expressions = self.expressions(expression)
3528        exists = " IF EXISTS " if expression.args.get("exists") else " "
3529        return f"DROP{exists}{expressions}"
3530
3531    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3532        return f"ADD {self.expressions(expression, indent=False)}"
3533
3534    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3535        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3536        return f"ADD {exists}{self.sql(expression.this)}"
3537
3538    def distinct_sql(self, expression: exp.Distinct) -> str:
3539        this = self.expressions(expression, flat=True)
3540
3541        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3542            case = exp.case()
3543            for arg in expression.expressions:
3544                case = case.when(arg.is_(exp.null()), exp.null())
3545            this = self.sql(case.else_(f"({this})"))
3546
3547        this = f" {this}" if this else ""
3548
3549        on = self.sql(expression, "on")
3550        on = f" ON {on}" if on else ""
3551        return f"DISTINCT{this}{on}"
3552
3553    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3554        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3555
3556    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3557        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3558
3559    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3560        this_sql = self.sql(expression, "this")
3561        expression_sql = self.sql(expression, "expression")
3562        kind = "MAX" if expression.args.get("max") else "MIN"
3563        return f"{this_sql} HAVING {kind} {expression_sql}"
3564
3565    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3566        return self.sql(
3567            exp.Cast(
3568                this=exp.Div(this=expression.this, expression=expression.expression),
3569                to=exp.DataType(this=exp.DataType.Type.INT),
3570            )
3571        )
3572
3573    def dpipe_sql(self, expression: exp.DPipe) -> str:
3574        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3575            return self.func(
3576                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3577            )
3578        return self.binary(expression, "||")
3579
3580    def div_sql(self, expression: exp.Div) -> str:
3581        l, r = expression.left, expression.right
3582
3583        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3584            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3585
3586        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3587            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3588                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3589
3590        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3591            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3592                return self.sql(
3593                    exp.cast(
3594                        l / r,
3595                        to=exp.DataType.Type.BIGINT,
3596                    )
3597                )
3598
3599        return self.binary(expression, "/")
3600
3601    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3602        n = exp._wrap(expression.this, exp.Binary)
3603        d = exp._wrap(expression.expression, exp.Binary)
3604        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3605
3606    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3607        return self.binary(expression, "OVERLAPS")
3608
3609    def distance_sql(self, expression: exp.Distance) -> str:
3610        return self.binary(expression, "<->")
3611
3612    def dot_sql(self, expression: exp.Dot) -> str:
3613        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3614
3615    def eq_sql(self, expression: exp.EQ) -> str:
3616        return self.binary(expression, "=")
3617
3618    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3619        return self.binary(expression, ":=")
3620
3621    def escape_sql(self, expression: exp.Escape) -> str:
3622        return self.binary(expression, "ESCAPE")
3623
3624    def glob_sql(self, expression: exp.Glob) -> str:
3625        return self.binary(expression, "GLOB")
3626
3627    def gt_sql(self, expression: exp.GT) -> str:
3628        return self.binary(expression, ">")
3629
3630    def gte_sql(self, expression: exp.GTE) -> str:
3631        return self.binary(expression, ">=")
3632
3633    def ilike_sql(self, expression: exp.ILike) -> str:
3634        return self.binary(expression, "ILIKE")
3635
3636    def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
3637        return self.binary(expression, "ILIKE ANY")
3638
3639    def is_sql(self, expression: exp.Is) -> str:
3640        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3641            return self.sql(
3642                expression.this if expression.expression.this else exp.not_(expression.this)
3643            )
3644        return self.binary(expression, "IS")
3645
3646    def like_sql(self, expression: exp.Like) -> str:
3647        return self.binary(expression, "LIKE")
3648
3649    def likeany_sql(self, expression: exp.LikeAny) -> str:
3650        return self.binary(expression, "LIKE ANY")
3651
3652    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3653        return self.binary(expression, "SIMILAR TO")
3654
3655    def lt_sql(self, expression: exp.LT) -> str:
3656        return self.binary(expression, "<")
3657
3658    def lte_sql(self, expression: exp.LTE) -> str:
3659        return self.binary(expression, "<=")
3660
3661    def mod_sql(self, expression: exp.Mod) -> str:
3662        return self.binary(expression, "%")
3663
3664    def mul_sql(self, expression: exp.Mul) -> str:
3665        return self.binary(expression, "*")
3666
3667    def neq_sql(self, expression: exp.NEQ) -> str:
3668        return self.binary(expression, "<>")
3669
3670    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3671        return self.binary(expression, "IS NOT DISTINCT FROM")
3672
3673    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3674        return self.binary(expression, "IS DISTINCT FROM")
3675
3676    def slice_sql(self, expression: exp.Slice) -> str:
3677        return self.binary(expression, ":")
3678
3679    def sub_sql(self, expression: exp.Sub) -> str:
3680        return self.binary(expression, "-")
3681
3682    def trycast_sql(self, expression: exp.TryCast) -> str:
3683        return self.cast_sql(expression, safe_prefix="TRY_")
3684
3685    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3686        return self.cast_sql(expression)
3687
3688    def try_sql(self, expression: exp.Try) -> str:
3689        if not self.TRY_SUPPORTED:
3690            self.unsupported("Unsupported TRY function")
3691            return self.sql(expression, "this")
3692
3693        return self.func("TRY", expression.this)
3694
3695    def log_sql(self, expression: exp.Log) -> str:
3696        this = expression.this
3697        expr = expression.expression
3698
3699        if self.dialect.LOG_BASE_FIRST is False:
3700            this, expr = expr, this
3701        elif self.dialect.LOG_BASE_FIRST is None and expr:
3702            if this.name in ("2", "10"):
3703                return self.func(f"LOG{this.name}", expr)
3704
3705            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3706
3707        return self.func("LOG", this, expr)
3708
3709    def use_sql(self, expression: exp.Use) -> str:
3710        kind = self.sql(expression, "kind")
3711        kind = f" {kind}" if kind else ""
3712        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3713        this = f" {this}" if this else ""
3714        return f"USE{kind}{this}"
3715
3716    def binary(self, expression: exp.Binary, op: str) -> str:
3717        sqls: t.List[str] = []
3718        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3719        binary_type = type(expression)
3720
3721        while stack:
3722            node = stack.pop()
3723
3724            if type(node) is binary_type:
3725                op_func = node.args.get("operator")
3726                if op_func:
3727                    op = f"OPERATOR({self.sql(op_func)})"
3728
3729                stack.append(node.right)
3730                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3731                stack.append(node.left)
3732            else:
3733                sqls.append(self.sql(node))
3734
3735        return "".join(sqls)
3736
3737    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3738        to_clause = self.sql(expression, "to")
3739        if to_clause:
3740            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3741
3742        return self.function_fallback_sql(expression)
3743
3744    def function_fallback_sql(self, expression: exp.Func) -> str:
3745        args = []
3746
3747        for key in expression.arg_types:
3748            arg_value = expression.args.get(key)
3749
3750            if isinstance(arg_value, list):
3751                for value in arg_value:
3752                    args.append(value)
3753            elif arg_value is not None:
3754                args.append(arg_value)
3755
3756        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3757            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3758        else:
3759            name = expression.sql_name()
3760
3761        return self.func(name, *args)
3762
3763    def func(
3764        self,
3765        name: str,
3766        *args: t.Optional[exp.Expression | str],
3767        prefix: str = "(",
3768        suffix: str = ")",
3769        normalize: bool = True,
3770    ) -> str:
3771        name = self.normalize_func(name) if normalize else name
3772        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3773
3774    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3775        arg_sqls = tuple(
3776            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3777        )
3778        if self.pretty and self.too_wide(arg_sqls):
3779            return self.indent(
3780                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3781            )
3782        return sep.join(arg_sqls)
3783
3784    def too_wide(self, args: t.Iterable) -> bool:
3785        return sum(len(arg) for arg in args) > self.max_text_width
3786
3787    def format_time(
3788        self,
3789        expression: exp.Expression,
3790        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3791        inverse_time_trie: t.Optional[t.Dict] = None,
3792    ) -> t.Optional[str]:
3793        return format_time(
3794            self.sql(expression, "format"),
3795            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3796            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3797        )
3798
3799    def expressions(
3800        self,
3801        expression: t.Optional[exp.Expression] = None,
3802        key: t.Optional[str] = None,
3803        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3804        flat: bool = False,
3805        indent: bool = True,
3806        skip_first: bool = False,
3807        skip_last: bool = False,
3808        sep: str = ", ",
3809        prefix: str = "",
3810        dynamic: bool = False,
3811        new_line: bool = False,
3812    ) -> str:
3813        expressions = expression.args.get(key or "expressions") if expression else sqls
3814
3815        if not expressions:
3816            return ""
3817
3818        if flat:
3819            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3820
3821        num_sqls = len(expressions)
3822        result_sqls = []
3823
3824        for i, e in enumerate(expressions):
3825            sql = self.sql(e, comment=False)
3826            if not sql:
3827                continue
3828
3829            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3830
3831            if self.pretty:
3832                if self.leading_comma:
3833                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3834                else:
3835                    result_sqls.append(
3836                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3837                    )
3838            else:
3839                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3840
3841        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3842            if new_line:
3843                result_sqls.insert(0, "")
3844                result_sqls.append("")
3845            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3846        else:
3847            result_sql = "".join(result_sqls)
3848
3849        return (
3850            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
3851            if indent
3852            else result_sql
3853        )
3854
3855    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
3856        flat = flat or isinstance(expression.parent, exp.Properties)
3857        expressions_sql = self.expressions(expression, flat=flat)
3858        if flat:
3859            return f"{op} {expressions_sql}"
3860        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3861
3862    def naked_property(self, expression: exp.Property) -> str:
3863        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
3864        if not property_name:
3865            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
3866        return f"{property_name} {self.sql(expression, 'this')}"
3867
3868    def tag_sql(self, expression: exp.Tag) -> str:
3869        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
3870
3871    def token_sql(self, token_type: TokenType) -> str:
3872        return self.TOKEN_MAPPING.get(token_type, token_type.name)
3873
3874    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
3875        this = self.sql(expression, "this")
3876        expressions = self.no_identify(self.expressions, expression)
3877        expressions = (
3878            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
3879        )
3880        return f"{this}{expressions}" if expressions.strip() != "" else this
3881
3882    def joinhint_sql(self, expression: exp.JoinHint) -> str:
3883        this = self.sql(expression, "this")
3884        expressions = self.expressions(expression, flat=True)
3885        return f"{this}({expressions})"
3886
3887    def kwarg_sql(self, expression: exp.Kwarg) -> str:
3888        return self.binary(expression, "=>")
3889
3890    def when_sql(self, expression: exp.When) -> str:
3891        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
3892        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
3893        condition = self.sql(expression, "condition")
3894        condition = f" AND {condition}" if condition else ""
3895
3896        then_expression = expression.args.get("then")
3897        if isinstance(then_expression, exp.Insert):
3898            this = self.sql(then_expression, "this")
3899            this = f"INSERT {this}" if this else "INSERT"
3900            then = self.sql(then_expression, "expression")
3901            then = f"{this} VALUES {then}" if then else this
3902        elif isinstance(then_expression, exp.Update):
3903            if isinstance(then_expression.args.get("expressions"), exp.Star):
3904                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
3905            else:
3906                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
3907        else:
3908            then = self.sql(then_expression)
3909        return f"WHEN {matched}{source}{condition} THEN {then}"
3910
3911    def whens_sql(self, expression: exp.Whens) -> str:
3912        return self.expressions(expression, sep=" ", indent=False)
3913
3914    def merge_sql(self, expression: exp.Merge) -> str:
3915        table = expression.this
3916        table_alias = ""
3917
3918        hints = table.args.get("hints")
3919        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
3920            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
3921            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
3922
3923        this = self.sql(table)
3924        using = f"USING {self.sql(expression, 'using')}"
3925        on = f"ON {self.sql(expression, 'on')}"
3926        whens = self.sql(expression, "whens")
3927
3928        returning = self.sql(expression, "returning")
3929        if returning:
3930            whens = f"{whens}{returning}"
3931
3932        sep = self.sep()
3933
3934        return self.prepend_ctes(
3935            expression,
3936            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
3937        )
3938
3939    @unsupported_args("format")
3940    def tochar_sql(self, expression: exp.ToChar) -> str:
3941        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
3942
3943    def tonumber_sql(self, expression: exp.ToNumber) -> str:
3944        if not self.SUPPORTS_TO_NUMBER:
3945            self.unsupported("Unsupported TO_NUMBER function")
3946            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3947
3948        fmt = expression.args.get("format")
3949        if not fmt:
3950            self.unsupported("Conversion format is required for TO_NUMBER")
3951            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3952
3953        return self.func("TO_NUMBER", expression.this, fmt)
3954
3955    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
3956        this = self.sql(expression, "this")
3957        kind = self.sql(expression, "kind")
3958        settings_sql = self.expressions(expression, key="settings", sep=" ")
3959        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
3960        return f"{this}({kind}{args})"
3961
3962    def dictrange_sql(self, expression: exp.DictRange) -> str:
3963        this = self.sql(expression, "this")
3964        max = self.sql(expression, "max")
3965        min = self.sql(expression, "min")
3966        return f"{this}(MIN {min} MAX {max})"
3967
3968    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
3969        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
3970
3971    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
3972        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
3973
3974    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
3975    def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
3976        return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
3977
3978    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
3979    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
3980        expressions = self.expressions(expression, flat=True)
3981        expressions = f" {self.wrap(expressions)}" if expressions else ""
3982        buckets = self.sql(expression, "buckets")
3983        kind = self.sql(expression, "kind")
3984        buckets = f" BUCKETS {buckets}" if buckets else ""
3985        order = self.sql(expression, "order")
3986        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3987
3988    def oncluster_sql(self, expression: exp.OnCluster) -> str:
3989        return ""
3990
3991    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
3992        expressions = self.expressions(expression, key="expressions", flat=True)
3993        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
3994        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
3995        buckets = self.sql(expression, "buckets")
3996        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3997
3998    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
3999        this = self.sql(expression, "this")
4000        having = self.sql(expression, "having")
4001
4002        if having:
4003            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4004
4005        return self.func("ANY_VALUE", this)
4006
4007    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4008        transform = self.func("TRANSFORM", *expression.expressions)
4009        row_format_before = self.sql(expression, "row_format_before")
4010        row_format_before = f" {row_format_before}" if row_format_before else ""
4011        record_writer = self.sql(expression, "record_writer")
4012        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4013        using = f" USING {self.sql(expression, 'command_script')}"
4014        schema = self.sql(expression, "schema")
4015        schema = f" AS {schema}" if schema else ""
4016        row_format_after = self.sql(expression, "row_format_after")
4017        row_format_after = f" {row_format_after}" if row_format_after else ""
4018        record_reader = self.sql(expression, "record_reader")
4019        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4020        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4021
4022    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4023        key_block_size = self.sql(expression, "key_block_size")
4024        if key_block_size:
4025            return f"KEY_BLOCK_SIZE = {key_block_size}"
4026
4027        using = self.sql(expression, "using")
4028        if using:
4029            return f"USING {using}"
4030
4031        parser = self.sql(expression, "parser")
4032        if parser:
4033            return f"WITH PARSER {parser}"
4034
4035        comment = self.sql(expression, "comment")
4036        if comment:
4037            return f"COMMENT {comment}"
4038
4039        visible = expression.args.get("visible")
4040        if visible is not None:
4041            return "VISIBLE" if visible else "INVISIBLE"
4042
4043        engine_attr = self.sql(expression, "engine_attr")
4044        if engine_attr:
4045            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4046
4047        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4048        if secondary_engine_attr:
4049            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4050
4051        self.unsupported("Unsupported index constraint option.")
4052        return ""
4053
4054    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4055        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4056        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4057
4058    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4059        kind = self.sql(expression, "kind")
4060        kind = f"{kind} INDEX" if kind else "INDEX"
4061        this = self.sql(expression, "this")
4062        this = f" {this}" if this else ""
4063        index_type = self.sql(expression, "index_type")
4064        index_type = f" USING {index_type}" if index_type else ""
4065        expressions = self.expressions(expression, flat=True)
4066        expressions = f" ({expressions})" if expressions else ""
4067        options = self.expressions(expression, key="options", sep=" ")
4068        options = f" {options}" if options else ""
4069        return f"{kind}{this}{index_type}{expressions}{options}"
4070
4071    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4072        if self.NVL2_SUPPORTED:
4073            return self.function_fallback_sql(expression)
4074
4075        case = exp.Case().when(
4076            expression.this.is_(exp.null()).not_(copy=False),
4077            expression.args["true"],
4078            copy=False,
4079        )
4080        else_cond = expression.args.get("false")
4081        if else_cond:
4082            case.else_(else_cond, copy=False)
4083
4084        return self.sql(case)
4085
4086    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4087        this = self.sql(expression, "this")
4088        expr = self.sql(expression, "expression")
4089        iterator = self.sql(expression, "iterator")
4090        condition = self.sql(expression, "condition")
4091        condition = f" IF {condition}" if condition else ""
4092        return f"{this} FOR {expr} IN {iterator}{condition}"
4093
4094    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4095        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4096
4097    def opclass_sql(self, expression: exp.Opclass) -> str:
4098        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4099
4100    def predict_sql(self, expression: exp.Predict) -> str:
4101        model = self.sql(expression, "this")
4102        model = f"MODEL {model}"
4103        table = self.sql(expression, "expression")
4104        table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4105        parameters = self.sql(expression, "params_struct")
4106        return self.func("PREDICT", model, table, parameters or None)
4107
4108    def forin_sql(self, expression: exp.ForIn) -> str:
4109        this = self.sql(expression, "this")
4110        expression_sql = self.sql(expression, "expression")
4111        return f"FOR {this} DO {expression_sql}"
4112
4113    def refresh_sql(self, expression: exp.Refresh) -> str:
4114        this = self.sql(expression, "this")
4115        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4116        return f"REFRESH {table}{this}"
4117
4118    def toarray_sql(self, expression: exp.ToArray) -> str:
4119        arg = expression.this
4120        if not arg.type:
4121            from sqlglot.optimizer.annotate_types import annotate_types
4122
4123            arg = annotate_types(arg, dialect=self.dialect)
4124
4125        if arg.is_type(exp.DataType.Type.ARRAY):
4126            return self.sql(arg)
4127
4128        cond_for_null = arg.is_(exp.null())
4129        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4130
4131    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4132        this = expression.this
4133        time_format = self.format_time(expression)
4134
4135        if time_format:
4136            return self.sql(
4137                exp.cast(
4138                    exp.StrToTime(this=this, format=expression.args["format"]),
4139                    exp.DataType.Type.TIME,
4140                )
4141            )
4142
4143        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4144            return self.sql(this)
4145
4146        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4147
4148    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4149        this = expression.this
4150        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4151            return self.sql(this)
4152
4153        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4154
4155    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4156        this = expression.this
4157        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4158            return self.sql(this)
4159
4160        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4161
4162    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4163        this = expression.this
4164        time_format = self.format_time(expression)
4165
4166        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4167            return self.sql(
4168                exp.cast(
4169                    exp.StrToTime(this=this, format=expression.args["format"]),
4170                    exp.DataType.Type.DATE,
4171                )
4172            )
4173
4174        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4175            return self.sql(this)
4176
4177        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4178
4179    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4180        return self.sql(
4181            exp.func(
4182                "DATEDIFF",
4183                expression.this,
4184                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4185                "day",
4186            )
4187        )
4188
4189    def lastday_sql(self, expression: exp.LastDay) -> str:
4190        if self.LAST_DAY_SUPPORTS_DATE_PART:
4191            return self.function_fallback_sql(expression)
4192
4193        unit = expression.text("unit")
4194        if unit and unit != "MONTH":
4195            self.unsupported("Date parts are not supported in LAST_DAY.")
4196
4197        return self.func("LAST_DAY", expression.this)
4198
4199    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4200        from sqlglot.dialects.dialect import unit_to_str
4201
4202        return self.func(
4203            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4204        )
4205
4206    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4207        if self.CAN_IMPLEMENT_ARRAY_ANY:
4208            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4209            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4210            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4211            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4212
4213        from sqlglot.dialects import Dialect
4214
4215        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4216        if self.dialect.__class__ != Dialect:
4217            self.unsupported("ARRAY_ANY is unsupported")
4218
4219        return self.function_fallback_sql(expression)
4220
4221    def struct_sql(self, expression: exp.Struct) -> str:
4222        expression.set(
4223            "expressions",
4224            [
4225                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4226                if isinstance(e, exp.PropertyEQ)
4227                else e
4228                for e in expression.expressions
4229            ],
4230        )
4231
4232        return self.function_fallback_sql(expression)
4233
4234    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4235        low = self.sql(expression, "this")
4236        high = self.sql(expression, "expression")
4237
4238        return f"{low} TO {high}"
4239
4240    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4241        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4242        tables = f" {self.expressions(expression)}"
4243
4244        exists = " IF EXISTS" if expression.args.get("exists") else ""
4245
4246        on_cluster = self.sql(expression, "cluster")
4247        on_cluster = f" {on_cluster}" if on_cluster else ""
4248
4249        identity = self.sql(expression, "identity")
4250        identity = f" {identity} IDENTITY" if identity else ""
4251
4252        option = self.sql(expression, "option")
4253        option = f" {option}" if option else ""
4254
4255        partition = self.sql(expression, "partition")
4256        partition = f" {partition}" if partition else ""
4257
4258        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4259
4260    # This transpiles T-SQL's CONVERT function
4261    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4262    def convert_sql(self, expression: exp.Convert) -> str:
4263        to = expression.this
4264        value = expression.expression
4265        style = expression.args.get("style")
4266        safe = expression.args.get("safe")
4267        strict = expression.args.get("strict")
4268
4269        if not to or not value:
4270            return ""
4271
4272        # Retrieve length of datatype and override to default if not specified
4273        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4274            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4275
4276        transformed: t.Optional[exp.Expression] = None
4277        cast = exp.Cast if strict else exp.TryCast
4278
4279        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4280        if isinstance(style, exp.Literal) and style.is_int:
4281            from sqlglot.dialects.tsql import TSQL
4282
4283            style_value = style.name
4284            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4285            if not converted_style:
4286                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4287
4288            fmt = exp.Literal.string(converted_style)
4289
4290            if to.this == exp.DataType.Type.DATE:
4291                transformed = exp.StrToDate(this=value, format=fmt)
4292            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4293                transformed = exp.StrToTime(this=value, format=fmt)
4294            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4295                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4296            elif to.this == exp.DataType.Type.TEXT:
4297                transformed = exp.TimeToStr(this=value, format=fmt)
4298
4299        if not transformed:
4300            transformed = cast(this=value, to=to, safe=safe)
4301
4302        return self.sql(transformed)
4303
4304    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4305        this = expression.this
4306        if isinstance(this, exp.JSONPathWildcard):
4307            this = self.json_path_part(this)
4308            return f".{this}" if this else ""
4309
4310        if exp.SAFE_IDENTIFIER_RE.match(this):
4311            return f".{this}"
4312
4313        this = self.json_path_part(this)
4314        return (
4315            f"[{this}]"
4316            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4317            else f".{this}"
4318        )
4319
4320    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4321        this = self.json_path_part(expression.this)
4322        return f"[{this}]" if this else ""
4323
4324    def _simplify_unless_literal(self, expression: E) -> E:
4325        if not isinstance(expression, exp.Literal):
4326            from sqlglot.optimizer.simplify import simplify
4327
4328            expression = simplify(expression, dialect=self.dialect)
4329
4330        return expression
4331
4332    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4333        this = expression.this
4334        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4335            self.unsupported(
4336                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4337            )
4338            return self.sql(this)
4339
4340        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4341            # The first modifier here will be the one closest to the AggFunc's arg
4342            mods = sorted(
4343                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4344                key=lambda x: 0
4345                if isinstance(x, exp.HavingMax)
4346                else (1 if isinstance(x, exp.Order) else 2),
4347            )
4348
4349            if mods:
4350                mod = mods[0]
4351                this = expression.__class__(this=mod.this.copy())
4352                this.meta["inline"] = True
4353                mod.this.replace(this)
4354                return self.sql(expression.this)
4355
4356            agg_func = expression.find(exp.AggFunc)
4357
4358            if agg_func:
4359                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4360                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4361
4362        return f"{self.sql(expression, 'this')} {text}"
4363
4364    def _replace_line_breaks(self, string: str) -> str:
4365        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4366        if self.pretty:
4367            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4368        return string
4369
4370    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4371        option = self.sql(expression, "this")
4372
4373        if expression.expressions:
4374            upper = option.upper()
4375
4376            # Snowflake FILE_FORMAT options are separated by whitespace
4377            sep = " " if upper == "FILE_FORMAT" else ", "
4378
4379            # Databricks copy/format options do not set their list of values with EQ
4380            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4381            values = self.expressions(expression, flat=True, sep=sep)
4382            return f"{option}{op}({values})"
4383
4384        value = self.sql(expression, "expression")
4385
4386        if not value:
4387            return option
4388
4389        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4390
4391        return f"{option}{op}{value}"
4392
4393    def credentials_sql(self, expression: exp.Credentials) -> str:
4394        cred_expr = expression.args.get("credentials")
4395        if isinstance(cred_expr, exp.Literal):
4396            # Redshift case: CREDENTIALS <string>
4397            credentials = self.sql(expression, "credentials")
4398            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4399        else:
4400            # Snowflake case: CREDENTIALS = (...)
4401            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4402            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4403
4404        storage = self.sql(expression, "storage")
4405        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4406
4407        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4408        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4409
4410        iam_role = self.sql(expression, "iam_role")
4411        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4412
4413        region = self.sql(expression, "region")
4414        region = f" REGION {region}" if region else ""
4415
4416        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4417
4418    def copy_sql(self, expression: exp.Copy) -> str:
4419        this = self.sql(expression, "this")
4420        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4421
4422        credentials = self.sql(expression, "credentials")
4423        credentials = self.seg(credentials) if credentials else ""
4424        kind = self.seg("FROM" if expression.args.get("kind") else "TO")
4425        files = self.expressions(expression, key="files", flat=True)
4426
4427        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4428        params = self.expressions(
4429            expression,
4430            key="params",
4431            sep=sep,
4432            new_line=True,
4433            skip_last=True,
4434            skip_first=True,
4435            indent=self.COPY_PARAMS_ARE_WRAPPED,
4436        )
4437
4438        if params:
4439            if self.COPY_PARAMS_ARE_WRAPPED:
4440                params = f" WITH ({params})"
4441            elif not self.pretty:
4442                params = f" {params}"
4443
4444        return f"COPY{this}{kind} {files}{credentials}{params}"
4445
4446    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4447        return ""
4448
4449    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4450        on_sql = "ON" if expression.args.get("on") else "OFF"
4451        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4452        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4453        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4454        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4455
4456        if filter_col or retention_period:
4457            on_sql = self.func("ON", filter_col, retention_period)
4458
4459        return f"DATA_DELETION={on_sql}"
4460
4461    def maskingpolicycolumnconstraint_sql(
4462        self, expression: exp.MaskingPolicyColumnConstraint
4463    ) -> str:
4464        this = self.sql(expression, "this")
4465        expressions = self.expressions(expression, flat=True)
4466        expressions = f" USING ({expressions})" if expressions else ""
4467        return f"MASKING POLICY {this}{expressions}"
4468
4469    def gapfill_sql(self, expression: exp.GapFill) -> str:
4470        this = self.sql(expression, "this")
4471        this = f"TABLE {this}"
4472        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4473
4474    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4475        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4476
4477    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4478        this = self.sql(expression, "this")
4479        expr = expression.expression
4480
4481        if isinstance(expr, exp.Func):
4482            # T-SQL's CLR functions are case sensitive
4483            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4484        else:
4485            expr = self.sql(expression, "expression")
4486
4487        return self.scope_resolution(expr, this)
4488
4489    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4490        if self.PARSE_JSON_NAME is None:
4491            return self.sql(expression.this)
4492
4493        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4494
4495    def rand_sql(self, expression: exp.Rand) -> str:
4496        lower = self.sql(expression, "lower")
4497        upper = self.sql(expression, "upper")
4498
4499        if lower and upper:
4500            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4501        return self.func("RAND", expression.this)
4502
4503    def changes_sql(self, expression: exp.Changes) -> str:
4504        information = self.sql(expression, "information")
4505        information = f"INFORMATION => {information}"
4506        at_before = self.sql(expression, "at_before")
4507        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4508        end = self.sql(expression, "end")
4509        end = f"{self.seg('')}{end}" if end else ""
4510
4511        return f"CHANGES ({information}){at_before}{end}"
4512
4513    def pad_sql(self, expression: exp.Pad) -> str:
4514        prefix = "L" if expression.args.get("is_left") else "R"
4515
4516        fill_pattern = self.sql(expression, "fill_pattern") or None
4517        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4518            fill_pattern = "' '"
4519
4520        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4521
4522    def summarize_sql(self, expression: exp.Summarize) -> str:
4523        table = " TABLE" if expression.args.get("table") else ""
4524        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4525
4526    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4527        generate_series = exp.GenerateSeries(**expression.args)
4528
4529        parent = expression.parent
4530        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4531            parent = parent.parent
4532
4533        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4534            return self.sql(exp.Unnest(expressions=[generate_series]))
4535
4536        if isinstance(parent, exp.Select):
4537            self.unsupported("GenerateSeries projection unnesting is not supported.")
4538
4539        return self.sql(generate_series)
4540
4541    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4542        exprs = expression.expressions
4543        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4544            rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4545        else:
4546            rhs = self.expressions(expression)
4547
4548        return self.func(name, expression.this, rhs or None)
4549
4550    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4551        if self.SUPPORTS_CONVERT_TIMEZONE:
4552            return self.function_fallback_sql(expression)
4553
4554        source_tz = expression.args.get("source_tz")
4555        target_tz = expression.args.get("target_tz")
4556        timestamp = expression.args.get("timestamp")
4557
4558        if source_tz and timestamp:
4559            timestamp = exp.AtTimeZone(
4560                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4561            )
4562
4563        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4564
4565        return self.sql(expr)
4566
4567    def json_sql(self, expression: exp.JSON) -> str:
4568        this = self.sql(expression, "this")
4569        this = f" {this}" if this else ""
4570
4571        _with = expression.args.get("with")
4572
4573        if _with is None:
4574            with_sql = ""
4575        elif not _with:
4576            with_sql = " WITHOUT"
4577        else:
4578            with_sql = " WITH"
4579
4580        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4581
4582        return f"JSON{this}{with_sql}{unique_sql}"
4583
4584    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4585        def _generate_on_options(arg: t.Any) -> str:
4586            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4587
4588        path = self.sql(expression, "path")
4589        returning = self.sql(expression, "returning")
4590        returning = f" RETURNING {returning}" if returning else ""
4591
4592        on_condition = self.sql(expression, "on_condition")
4593        on_condition = f" {on_condition}" if on_condition else ""
4594
4595        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4596
4597    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4598        else_ = "ELSE " if expression.args.get("else_") else ""
4599        condition = self.sql(expression, "expression")
4600        condition = f"WHEN {condition} THEN " if condition else else_
4601        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4602        return f"{condition}{insert}"
4603
4604    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4605        kind = self.sql(expression, "kind")
4606        expressions = self.seg(self.expressions(expression, sep=" "))
4607        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4608        return res
4609
4610    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4611        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4612        empty = expression.args.get("empty")
4613        empty = (
4614            f"DEFAULT {empty} ON EMPTY"
4615            if isinstance(empty, exp.Expression)
4616            else self.sql(expression, "empty")
4617        )
4618
4619        error = expression.args.get("error")
4620        error = (
4621            f"DEFAULT {error} ON ERROR"
4622            if isinstance(error, exp.Expression)
4623            else self.sql(expression, "error")
4624        )
4625
4626        if error and empty:
4627            error = (
4628                f"{empty} {error}"
4629                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4630                else f"{error} {empty}"
4631            )
4632            empty = ""
4633
4634        null = self.sql(expression, "null")
4635
4636        return f"{empty}{error}{null}"
4637
4638    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4639        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4640        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
4641
4642    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4643        this = self.sql(expression, "this")
4644        path = self.sql(expression, "path")
4645
4646        passing = self.expressions(expression, "passing")
4647        passing = f" PASSING {passing}" if passing else ""
4648
4649        on_condition = self.sql(expression, "on_condition")
4650        on_condition = f" {on_condition}" if on_condition else ""
4651
4652        path = f"{path}{passing}{on_condition}"
4653
4654        return self.func("JSON_EXISTS", this, path)
4655
4656    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4657        array_agg = self.function_fallback_sql(expression)
4658
4659        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4660        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4661        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4662            parent = expression.parent
4663            if isinstance(parent, exp.Filter):
4664                parent_cond = parent.expression.this
4665                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4666            else:
4667                this = expression.this
4668                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4669                if this.find(exp.Column):
4670                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4671                    this_sql = (
4672                        self.expressions(this)
4673                        if isinstance(this, exp.Distinct)
4674                        else self.sql(expression, "this")
4675                    )
4676
4677                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4678
4679        return array_agg
4680
4681    def apply_sql(self, expression: exp.Apply) -> str:
4682        this = self.sql(expression, "this")
4683        expr = self.sql(expression, "expression")
4684
4685        return f"{this} APPLY({expr})"
4686
4687    def grant_sql(self, expression: exp.Grant) -> str:
4688        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4689
4690        kind = self.sql(expression, "kind")
4691        kind = f" {kind}" if kind else ""
4692
4693        securable = self.sql(expression, "securable")
4694        securable = f" {securable}" if securable else ""
4695
4696        principals = self.expressions(expression, key="principals", flat=True)
4697
4698        grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4699
4700        return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4701
4702    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4703        this = self.sql(expression, "this")
4704        columns = self.expressions(expression, flat=True)
4705        columns = f"({columns})" if columns else ""
4706
4707        return f"{this}{columns}"
4708
4709    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4710        this = self.sql(expression, "this")
4711
4712        kind = self.sql(expression, "kind")
4713        kind = f"{kind} " if kind else ""
4714
4715        return f"{kind}{this}"
4716
4717    def columns_sql(self, expression: exp.Columns):
4718        func = self.function_fallback_sql(expression)
4719        if expression.args.get("unpack"):
4720            func = f"*{func}"
4721
4722        return func
4723
4724    def overlay_sql(self, expression: exp.Overlay):
4725        this = self.sql(expression, "this")
4726        expr = self.sql(expression, "expression")
4727        from_sql = self.sql(expression, "from")
4728        for_sql = self.sql(expression, "for")
4729        for_sql = f" FOR {for_sql}" if for_sql else ""
4730
4731        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
4732
4733    @unsupported_args("format")
4734    def todouble_sql(self, expression: exp.ToDouble) -> str:
4735        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4736
4737    def string_sql(self, expression: exp.String) -> str:
4738        this = expression.this
4739        zone = expression.args.get("zone")
4740
4741        if zone:
4742            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4743            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4744            # set for source_tz to transpile the time conversion before the STRING cast
4745            this = exp.ConvertTimezone(
4746                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4747            )
4748
4749        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
4750
4751    def median_sql(self, expression: exp.Median):
4752        if not self.SUPPORTS_MEDIAN:
4753            return self.sql(
4754                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4755            )
4756
4757        return self.function_fallback_sql(expression)
4758
4759    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4760        filler = self.sql(expression, "this")
4761        filler = f" {filler}" if filler else ""
4762        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4763        return f"TRUNCATE{filler} {with_count}"
4764
4765    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
4766        if self.SUPPORTS_UNIX_SECONDS:
4767            return self.function_fallback_sql(expression)
4768
4769        start_ts = exp.cast(
4770            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
4771        )
4772
4773        return self.sql(
4774            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
4775        )
4776
4777    def arraysize_sql(self, expression: exp.ArraySize) -> str:
4778        dim = expression.expression
4779
4780        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
4781        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
4782            if not (dim.is_int and dim.name == "1"):
4783                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
4784            dim = None
4785
4786        # If dimension is required but not specified, default initialize it
4787        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
4788            dim = exp.Literal.number(1)
4789
4790        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4791
4792    def attach_sql(self, expression: exp.Attach) -> str:
4793        this = self.sql(expression, "this")
4794        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
4795        expressions = self.expressions(expression)
4796        expressions = f" ({expressions})" if expressions else ""
4797
4798        return f"ATTACH{exists_sql} {this}{expressions}"
4799
4800    def detach_sql(self, expression: exp.Detach) -> str:
4801        this = self.sql(expression, "this")
4802        # the DATABASE keyword is required if IF EXISTS is set
4803        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
4804        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
4805        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
4806
4807        return f"DETACH{exists_sql} {this}"
4808
4809    def attachoption_sql(self, expression: exp.AttachOption) -> str:
4810        this = self.sql(expression, "this")
4811        value = self.sql(expression, "expression")
4812        value = f" {value}" if value else ""
4813        return f"{this}{value}"
4814
4815    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4816        this_sql = self.sql(expression, "this")
4817        if isinstance(expression.this, exp.Table):
4818            this_sql = f"TABLE {this_sql}"
4819
4820        return self.func(
4821            "FEATURES_AT_TIME",
4822            this_sql,
4823            expression.args.get("time"),
4824            expression.args.get("num_rows"),
4825            expression.args.get("ignore_feature_nulls"),
4826        )
4827
4828    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4829        return (
4830            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
4831        )
4832
4833    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
4834        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
4835        encode = f"{encode} {self.sql(expression, 'this')}"
4836
4837        properties = expression.args.get("properties")
4838        if properties:
4839            encode = f"{encode} {self.properties(properties)}"
4840
4841        return encode
4842
4843    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
4844        this = self.sql(expression, "this")
4845        include = f"INCLUDE {this}"
4846
4847        column_def = self.sql(expression, "column_def")
4848        if column_def:
4849            include = f"{include} {column_def}"
4850
4851        alias = self.sql(expression, "alias")
4852        if alias:
4853            include = f"{include} AS {alias}"
4854
4855        return include
4856
4857    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
4858        name = f"NAME {self.sql(expression, 'this')}"
4859        return self.func("XMLELEMENT", name, *expression.expressions)
4860
4861    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
4862        this = self.sql(expression, "this")
4863        expr = self.sql(expression, "expression")
4864        expr = f"({expr})" if expr else ""
4865        return f"{this}{expr}"
4866
4867    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
4868        partitions = self.expressions(expression, "partition_expressions")
4869        create = self.expressions(expression, "create_expressions")
4870        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
4871
4872    def partitionbyrangepropertydynamic_sql(
4873        self, expression: exp.PartitionByRangePropertyDynamic
4874    ) -> str:
4875        start = self.sql(expression, "start")
4876        end = self.sql(expression, "end")
4877
4878        every = expression.args["every"]
4879        if isinstance(every, exp.Interval) and every.this.is_string:
4880            every.this.replace(exp.Literal.number(every.name))
4881
4882        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4883
4884    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
4885        name = self.sql(expression, "this")
4886        values = self.expressions(expression, flat=True)
4887
4888        return f"NAME {name} VALUE {values}"
4889
4890    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
4891        kind = self.sql(expression, "kind")
4892        sample = self.sql(expression, "sample")
4893        return f"SAMPLE {sample} {kind}"
4894
4895    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
4896        kind = self.sql(expression, "kind")
4897        option = self.sql(expression, "option")
4898        option = f" {option}" if option else ""
4899        this = self.sql(expression, "this")
4900        this = f" {this}" if this else ""
4901        columns = self.expressions(expression)
4902        columns = f" {columns}" if columns else ""
4903        return f"{kind}{option} STATISTICS{this}{columns}"
4904
4905    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
4906        this = self.sql(expression, "this")
4907        columns = self.expressions(expression)
4908        inner_expression = self.sql(expression, "expression")
4909        inner_expression = f" {inner_expression}" if inner_expression else ""
4910        update_options = self.sql(expression, "update_options")
4911        update_options = f" {update_options} UPDATE" if update_options else ""
4912        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
4913
4914    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
4915        kind = self.sql(expression, "kind")
4916        kind = f" {kind}" if kind else ""
4917        return f"DELETE{kind} STATISTICS"
4918
4919    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
4920        inner_expression = self.sql(expression, "expression")
4921        return f"LIST CHAINED ROWS{inner_expression}"
4922
4923    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
4924        kind = self.sql(expression, "kind")
4925        this = self.sql(expression, "this")
4926        this = f" {this}" if this else ""
4927        inner_expression = self.sql(expression, "expression")
4928        return f"VALIDATE {kind}{this}{inner_expression}"
4929
4930    def analyze_sql(self, expression: exp.Analyze) -> str:
4931        options = self.expressions(expression, key="options", sep=" ")
4932        options = f" {options}" if options else ""
4933        kind = self.sql(expression, "kind")
4934        kind = f" {kind}" if kind else ""
4935        this = self.sql(expression, "this")
4936        this = f" {this}" if this else ""
4937        mode = self.sql(expression, "mode")
4938        mode = f" {mode}" if mode else ""
4939        properties = self.sql(expression, "properties")
4940        properties = f" {properties}" if properties else ""
4941        partition = self.sql(expression, "partition")
4942        partition = f" {partition}" if partition else ""
4943        inner_expression = self.sql(expression, "expression")
4944        inner_expression = f" {inner_expression}" if inner_expression else ""
4945        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4946
4947    def xmltable_sql(self, expression: exp.XMLTable) -> str:
4948        this = self.sql(expression, "this")
4949        namespaces = self.expressions(expression, key="namespaces")
4950        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
4951        passing = self.expressions(expression, key="passing")
4952        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
4953        columns = self.expressions(expression, key="columns")
4954        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
4955        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4956        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4957
4958    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4959        this = self.sql(expression, "this")
4960        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
4961
4962    def export_sql(self, expression: exp.Export) -> str:
4963        this = self.sql(expression, "this")
4964        connection = self.sql(expression, "connection")
4965        connection = f"WITH CONNECTION {connection} " if connection else ""
4966        options = self.sql(expression, "options")
4967        return f"EXPORT DATA {connection}{options} AS {this}"
4968
4969    def declare_sql(self, expression: exp.Declare) -> str:
4970        return f"DECLARE {self.expressions(expression, flat=True)}"
4971
4972    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
4973        variable = self.sql(expression, "this")
4974        default = self.sql(expression, "default")
4975        default = f" = {default}" if default else ""
4976
4977        kind = self.sql(expression, "kind")
4978        if isinstance(expression.args.get("kind"), exp.Schema):
4979            kind = f"TABLE {kind}"
4980
4981        return f"{variable} AS {kind}{default}"
4982
4983    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
4984        kind = self.sql(expression, "kind")
4985        this = self.sql(expression, "this")
4986        set = self.sql(expression, "expression")
4987        using = self.sql(expression, "using")
4988        using = f" USING {using}" if using else ""
4989
4990        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
4991
4992        return f"{kind_sql} {this} SET {set}{using}"
4993
4994    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
4995        params = self.expressions(expression, key="params", flat=True)
4996        return self.func(expression.name, *expression.expressions) + f"({params})"
4997
4998    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
4999        return self.func(expression.name, *expression.expressions)
5000
5001    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5002        return self.anonymousaggfunc_sql(expression)
5003
5004    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5005        return self.parameterizedagg_sql(expression)
5006
5007    def show_sql(self, expression: exp.Show) -> str:
5008        self.unsupported("Unsupported SHOW statement")
5009        return ""
5010
5011    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5012        # Snowflake GET/PUT statements:
5013        #   PUT <file> <internalStage> <properties>
5014        #   GET <internalStage> <file> <properties>
5015        props = expression.args.get("properties")
5016        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5017        this = self.sql(expression, "this")
5018        target = self.sql(expression, "target")
5019
5020        if isinstance(expression, exp.Put):
5021            return f"PUT {this} {target}{props_sql}"
5022        else:
5023            return f"GET {target} {this}{props_sql}"
5024
5025    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5026        this = self.sql(expression, "this")
5027        expr = self.sql(expression, "expression")
5028        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5029        return f"TRANSLATE({this} USING {expr}{with_error})"
5030
5031    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5032        if self.SUPPORTS_DECODE_CASE:
5033            return self.func("DECODE", *expression.expressions)
5034
5035        expression, *expressions = expression.expressions
5036
5037        ifs = []
5038        for search, result in zip(expressions[::2], expressions[1::2]):
5039            if isinstance(search, exp.Literal):
5040                ifs.append(exp.If(this=expression.eq(search), true=result))
5041            elif isinstance(search, exp.Null):
5042                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5043            else:
5044                if isinstance(search, exp.Binary):
5045                    search = exp.paren(search)
5046
5047                cond = exp.or_(
5048                    expression.eq(search),
5049                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5050                    copy=False,
5051                )
5052                ifs.append(exp.If(this=cond, true=result))
5053
5054        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5055        return self.sql(case)
5056
5057    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5058        this = self.sql(expression, "this")
5059        this = self.seg(this, sep="")
5060        dimensions = self.expressions(
5061            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5062        )
5063        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5064        metrics = self.expressions(
5065            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5066        )
5067        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5068        where = self.sql(expression, "where")
5069        where = self.seg(f"WHERE {where}") if where else ""
5070        return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
logger = <Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE = re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
def unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args(
31    *args: t.Union[str, t.Tuple[str, str]],
32) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
33    """
34    Decorator that can be used to mark certain args of an `Expression` subclass as unsupported.
35    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
36    """
37    diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {}
38    for arg in args:
39        if isinstance(arg, str):
40            diagnostic_by_arg[arg] = None
41        else:
42            diagnostic_by_arg[arg[0]] = arg[1]
43
44    def decorator(func: GeneratorMethod) -> GeneratorMethod:
45        @wraps(func)
46        def _func(generator: G, expression: E) -> str:
47            expression_name = expression.__class__.__name__
48            dialect_name = generator.dialect.__class__.__name__
49
50            for arg_name, diagnostic in diagnostic_by_arg.items():
51                if expression.args.get(arg_name):
52                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
53                        arg_name, expression_name, dialect_name
54                    )
55                    generator.unsupported(diagnostic)
56
57            return func(generator, expression)
58
59        return _func
60
61    return decorator

Decorator that can be used to mark certain args of an Expression subclass as unsupported. It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).

class Generator:
  75class Generator(metaclass=_Generator):
  76    """
  77    Generator converts a given syntax tree to the corresponding SQL string.
  78
  79    Args:
  80        pretty: Whether to format the produced SQL string.
  81            Default: False.
  82        identify: Determines when an identifier should be quoted. Possible values are:
  83            False (default): Never quote, except in cases where it's mandatory by the dialect.
  84            True or 'always': Always quote.
  85            'safe': Only quote identifiers that are case insensitive.
  86        normalize: Whether to normalize identifiers to lowercase.
  87            Default: False.
  88        pad: The pad size in a formatted string. For example, this affects the indentation of
  89            a projection in a query, relative to its nesting level.
  90            Default: 2.
  91        indent: The indentation size in a formatted string. For example, this affects the
  92            indentation of subqueries and filters under a `WHERE` clause.
  93            Default: 2.
  94        normalize_functions: How to normalize function names. Possible values are:
  95            "upper" or True (default): Convert names to uppercase.
  96            "lower": Convert names to lowercase.
  97            False: Disables function name normalization.
  98        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
  99            Default ErrorLevel.WARN.
 100        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 101            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 102            Default: 3
 103        leading_comma: Whether the comma is leading or trailing in select expressions.
 104            This is only relevant when generating in pretty mode.
 105            Default: False
 106        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 107            The default is on the smaller end because the length only represents a segment and not the true
 108            line length.
 109            Default: 80
 110        comments: Whether to preserve comments in the output SQL code.
 111            Default: True
 112    """
 113
 114    TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = {
 115        **JSON_PATH_PART_TRANSFORMS,
 116        exp.AllowedValuesProperty: lambda self,
 117        e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}",
 118        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 119        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 120        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 121        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 122        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 123        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 124        exp.CaseSpecificColumnConstraint: lambda _,
 125        e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC",
 126        exp.Ceil: lambda self, e: self.ceil_floor(e),
 127        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 128        exp.CharacterSetProperty: lambda self,
 129        e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}",
 130        exp.ClusteredColumnConstraint: lambda self,
 131        e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})",
 132        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 133        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 134        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 135        exp.ConvertToCharset: lambda self, e: self.func(
 136            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 137        ),
 138        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 139        exp.CredentialsProperty: lambda self,
 140        e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})",
 141        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 142        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 143        exp.DynamicProperty: lambda *_: "DYNAMIC",
 144        exp.EmptyProperty: lambda *_: "EMPTY",
 145        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 146        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 147        exp.EphemeralColumnConstraint: lambda self,
 148        e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}",
 149        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 150        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 151        exp.Except: lambda self, e: self.set_operations(e),
 152        exp.ExternalProperty: lambda *_: "EXTERNAL",
 153        exp.Floor: lambda self, e: self.ceil_floor(e),
 154        exp.Get: lambda self, e: self.get_put_sql(e),
 155        exp.GlobalProperty: lambda *_: "GLOBAL",
 156        exp.HeapProperty: lambda *_: "HEAP",
 157        exp.IcebergProperty: lambda *_: "ICEBERG",
 158        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 159        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 160        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 161        exp.Intersect: lambda self, e: self.set_operations(e),
 162        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 163        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)),
 164        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 165        exp.LocationProperty: lambda self, e: self.naked_property(e),
 166        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 167        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 168        exp.NonClusteredColumnConstraint: lambda self,
 169        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 170        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 171        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 172        exp.OnCommitProperty: lambda _,
 173        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 174        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 175        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 176        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 177        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 178        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 179        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 180        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 181        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 182        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 183        exp.ProjectionPolicyColumnConstraint: lambda self,
 184        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 185        exp.Put: lambda self, e: self.get_put_sql(e),
 186        exp.RemoteWithConnectionModelProperty: lambda self,
 187        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 188        exp.ReturnsProperty: lambda self, e: (
 189            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 190        ),
 191        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 192        exp.SecureProperty: lambda *_: "SECURE",
 193        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 194        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 195        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 196        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 197        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 198        exp.SqlReadWriteProperty: lambda _, e: e.name,
 199        exp.SqlSecurityProperty: lambda _,
 200        e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
 201        exp.StabilityProperty: lambda _, e: e.name,
 202        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 203        exp.StreamingTableProperty: lambda *_: "STREAMING",
 204        exp.StrictProperty: lambda *_: "STRICT",
 205        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 206        exp.TableColumn: lambda self, e: self.sql(e.this),
 207        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 208        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 209        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 210        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 211        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 212        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 213        exp.TransientProperty: lambda *_: "TRANSIENT",
 214        exp.Union: lambda self, e: self.set_operations(e),
 215        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 216        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 217        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 218        exp.Uuid: lambda *_: "UUID()",
 219        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 220        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 221        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 222        exp.VolatileProperty: lambda *_: "VOLATILE",
 223        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 224        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 225        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 226        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 227        exp.ForceProperty: lambda *_: "FORCE",
 228    }
 229
 230    # Whether null ordering is supported in order by
 231    # True: Full Support, None: No support, False: No support for certain cases
 232    # such as window specifications, aggregate functions etc
 233    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 234
 235    # Whether ignore nulls is inside the agg or outside.
 236    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 237    IGNORE_NULLS_IN_FUNC = False
 238
 239    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 240    LOCKING_READS_SUPPORTED = False
 241
 242    # Whether the EXCEPT and INTERSECT operations can return duplicates
 243    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 244
 245    # Wrap derived values in parens, usually standard but spark doesn't support it
 246    WRAP_DERIVED_VALUES = True
 247
 248    # Whether create function uses an AS before the RETURN
 249    CREATE_FUNCTION_RETURN_AS = True
 250
 251    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 252    MATCHED_BY_SOURCE = True
 253
 254    # Whether the INTERVAL expression works only with values like '1 day'
 255    SINGLE_STRING_INTERVAL = False
 256
 257    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 258    INTERVAL_ALLOWS_PLURAL_FORM = True
 259
 260    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 261    LIMIT_FETCH = "ALL"
 262
 263    # Whether limit and fetch allows expresions or just limits
 264    LIMIT_ONLY_LITERALS = False
 265
 266    # Whether a table is allowed to be renamed with a db
 267    RENAME_TABLE_WITH_DB = True
 268
 269    # The separator for grouping sets and rollups
 270    GROUPINGS_SEP = ","
 271
 272    # The string used for creating an index on a table
 273    INDEX_ON = "ON"
 274
 275    # Whether join hints should be generated
 276    JOIN_HINTS = True
 277
 278    # Whether table hints should be generated
 279    TABLE_HINTS = True
 280
 281    # Whether query hints should be generated
 282    QUERY_HINTS = True
 283
 284    # What kind of separator to use for query hints
 285    QUERY_HINT_SEP = ", "
 286
 287    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 288    IS_BOOL_ALLOWED = True
 289
 290    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 291    DUPLICATE_KEY_UPDATE_WITH_SET = True
 292
 293    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 294    LIMIT_IS_TOP = False
 295
 296    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 297    RETURNING_END = True
 298
 299    # Whether to generate an unquoted value for EXTRACT's date part argument
 300    EXTRACT_ALLOWS_QUOTES = True
 301
 302    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 303    TZ_TO_WITH_TIME_ZONE = False
 304
 305    # Whether the NVL2 function is supported
 306    NVL2_SUPPORTED = True
 307
 308    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 309    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 310
 311    # Whether VALUES statements can be used as derived tables.
 312    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 313    # SELECT * VALUES into SELECT UNION
 314    VALUES_AS_TABLE = True
 315
 316    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 317    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 318
 319    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 320    UNNEST_WITH_ORDINALITY = True
 321
 322    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 323    AGGREGATE_FILTER_SUPPORTED = True
 324
 325    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 326    SEMI_ANTI_JOIN_WITH_SIDE = True
 327
 328    # Whether to include the type of a computed column in the CREATE DDL
 329    COMPUTED_COLUMN_WITH_TYPE = True
 330
 331    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 332    SUPPORTS_TABLE_COPY = True
 333
 334    # Whether parentheses are required around the table sample's expression
 335    TABLESAMPLE_REQUIRES_PARENS = True
 336
 337    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 338    TABLESAMPLE_SIZE_IS_ROWS = True
 339
 340    # The keyword(s) to use when generating a sample clause
 341    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 342
 343    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 344    TABLESAMPLE_WITH_METHOD = True
 345
 346    # The keyword to use when specifying the seed of a sample clause
 347    TABLESAMPLE_SEED_KEYWORD = "SEED"
 348
 349    # Whether COLLATE is a function instead of a binary operator
 350    COLLATE_IS_FUNC = False
 351
 352    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 353    DATA_TYPE_SPECIFIERS_ALLOWED = False
 354
 355    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 356    ENSURE_BOOLS = False
 357
 358    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 359    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 360
 361    # Whether CONCAT requires >1 arguments
 362    SUPPORTS_SINGLE_ARG_CONCAT = True
 363
 364    # Whether LAST_DAY function supports a date part argument
 365    LAST_DAY_SUPPORTS_DATE_PART = True
 366
 367    # Whether named columns are allowed in table aliases
 368    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 369
 370    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 371    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 372
 373    # What delimiter to use for separating JSON key/value pairs
 374    JSON_KEY_VALUE_PAIR_SEP = ":"
 375
 376    # INSERT OVERWRITE TABLE x override
 377    INSERT_OVERWRITE = " OVERWRITE TABLE"
 378
 379    # Whether the SELECT .. INTO syntax is used instead of CTAS
 380    SUPPORTS_SELECT_INTO = False
 381
 382    # Whether UNLOGGED tables can be created
 383    SUPPORTS_UNLOGGED_TABLES = False
 384
 385    # Whether the CREATE TABLE LIKE statement is supported
 386    SUPPORTS_CREATE_TABLE_LIKE = True
 387
 388    # Whether the LikeProperty needs to be specified inside of the schema clause
 389    LIKE_PROPERTY_INSIDE_SCHEMA = False
 390
 391    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 392    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 393    MULTI_ARG_DISTINCT = True
 394
 395    # Whether the JSON extraction operators expect a value of type JSON
 396    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 397
 398    # Whether bracketed keys like ["foo"] are supported in JSON paths
 399    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 400
 401    # Whether to escape keys using single quotes in JSON paths
 402    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 403
 404    # The JSONPathPart expressions supported by this dialect
 405    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 406
 407    # Whether any(f(x) for x in array) can be implemented by this dialect
 408    CAN_IMPLEMENT_ARRAY_ANY = False
 409
 410    # Whether the function TO_NUMBER is supported
 411    SUPPORTS_TO_NUMBER = True
 412
 413    # Whether EXCLUDE in window specification is supported
 414    SUPPORTS_WINDOW_EXCLUDE = False
 415
 416    # Whether or not set op modifiers apply to the outer set op or select.
 417    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 418    # True means limit 1 happens after the set op, False means it it happens on y.
 419    SET_OP_MODIFIERS = True
 420
 421    # Whether parameters from COPY statement are wrapped in parentheses
 422    COPY_PARAMS_ARE_WRAPPED = True
 423
 424    # Whether values of params are set with "=" token or empty space
 425    COPY_PARAMS_EQ_REQUIRED = False
 426
 427    # Whether COPY statement has INTO keyword
 428    COPY_HAS_INTO_KEYWORD = True
 429
 430    # Whether the conditional TRY(expression) function is supported
 431    TRY_SUPPORTED = True
 432
 433    # Whether the UESCAPE syntax in unicode strings is supported
 434    SUPPORTS_UESCAPE = True
 435
 436    # The keyword to use when generating a star projection with excluded columns
 437    STAR_EXCEPT = "EXCEPT"
 438
 439    # The HEX function name
 440    HEX_FUNC = "HEX"
 441
 442    # The keywords to use when prefixing & separating WITH based properties
 443    WITH_PROPERTIES_PREFIX = "WITH"
 444
 445    # Whether to quote the generated expression of exp.JsonPath
 446    QUOTE_JSON_PATH = True
 447
 448    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 449    PAD_FILL_PATTERN_IS_REQUIRED = False
 450
 451    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 452    SUPPORTS_EXPLODING_PROJECTIONS = True
 453
 454    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 455    ARRAY_CONCAT_IS_VAR_LEN = True
 456
 457    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 458    SUPPORTS_CONVERT_TIMEZONE = False
 459
 460    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 461    SUPPORTS_MEDIAN = True
 462
 463    # Whether UNIX_SECONDS(timestamp) is supported
 464    SUPPORTS_UNIX_SECONDS = False
 465
 466    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 467    ALTER_SET_WRAPPED = False
 468
 469    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 470    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 471    # TODO: The normalization should be done by default once we've tested it across all dialects.
 472    NORMALIZE_EXTRACT_DATE_PARTS = False
 473
 474    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 475    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 476
 477    # The function name of the exp.ArraySize expression
 478    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 479
 480    # The syntax to use when altering the type of a column
 481    ALTER_SET_TYPE = "SET DATA TYPE"
 482
 483    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 484    # None -> Doesn't support it at all
 485    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 486    # True (Postgres) -> Explicitly requires it
 487    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 488
 489    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 490    SUPPORTS_DECODE_CASE = True
 491
 492    TYPE_MAPPING = {
 493        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 494        exp.DataType.Type.NCHAR: "CHAR",
 495        exp.DataType.Type.NVARCHAR: "VARCHAR",
 496        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 497        exp.DataType.Type.LONGTEXT: "TEXT",
 498        exp.DataType.Type.TINYTEXT: "TEXT",
 499        exp.DataType.Type.BLOB: "VARBINARY",
 500        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 501        exp.DataType.Type.LONGBLOB: "BLOB",
 502        exp.DataType.Type.TINYBLOB: "BLOB",
 503        exp.DataType.Type.INET: "INET",
 504        exp.DataType.Type.ROWVERSION: "VARBINARY",
 505        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 506    }
 507
 508    TIME_PART_SINGULARS = {
 509        "MICROSECONDS": "MICROSECOND",
 510        "SECONDS": "SECOND",
 511        "MINUTES": "MINUTE",
 512        "HOURS": "HOUR",
 513        "DAYS": "DAY",
 514        "WEEKS": "WEEK",
 515        "MONTHS": "MONTH",
 516        "QUARTERS": "QUARTER",
 517        "YEARS": "YEAR",
 518    }
 519
 520    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 521        "cluster": lambda self, e: self.sql(e, "cluster"),
 522        "distribute": lambda self, e: self.sql(e, "distribute"),
 523        "sort": lambda self, e: self.sql(e, "sort"),
 524        "windows": lambda self, e: (
 525            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 526            if e.args.get("windows")
 527            else ""
 528        ),
 529        "qualify": lambda self, e: self.sql(e, "qualify"),
 530    }
 531
 532    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 533
 534    STRUCT_DELIMITER = ("<", ">")
 535
 536    PARAMETER_TOKEN = "@"
 537    NAMED_PLACEHOLDER_TOKEN = ":"
 538
 539    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 540
 541    PROPERTIES_LOCATION = {
 542        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 543        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 544        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 545        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 546        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 547        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 548        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 549        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 550        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 551        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 552        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 553        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 554        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 555        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 556        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 557        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 558        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 559        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 560        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 561        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 562        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 563        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 564        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 565        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 566        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 567        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 568        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 569        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 570        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 571        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 572        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 573        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 574        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 575        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 576        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 577        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 578        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 579        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 580        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 581        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 582        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 583        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 584        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 585        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 586        exp.LogProperty: exp.Properties.Location.POST_NAME,
 587        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 588        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 589        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 590        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 591        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 592        exp.Order: exp.Properties.Location.POST_SCHEMA,
 593        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 594        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 595        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 596        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 597        exp.Property: exp.Properties.Location.POST_WITH,
 598        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 599        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 600        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 602        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 604        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 605        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 606        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 608        exp.Set: exp.Properties.Location.POST_SCHEMA,
 609        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 610        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 611        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 612        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 613        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 614        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 615        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 616        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 617        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 618        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 619        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 620        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 621        exp.Tags: exp.Properties.Location.POST_WITH,
 622        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 623        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 624        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 625        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 626        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 627        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 628        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 629        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 630        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 631        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 632        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 633        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 634        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 635        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 636        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 637    }
 638
 639    # Keywords that can't be used as unquoted identifier names
 640    RESERVED_KEYWORDS: t.Set[str] = set()
 641
 642    # Expressions whose comments are separated from them for better formatting
 643    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 644        exp.Command,
 645        exp.Create,
 646        exp.Describe,
 647        exp.Delete,
 648        exp.Drop,
 649        exp.From,
 650        exp.Insert,
 651        exp.Join,
 652        exp.MultitableInserts,
 653        exp.Order,
 654        exp.Group,
 655        exp.Having,
 656        exp.Select,
 657        exp.SetOperation,
 658        exp.Update,
 659        exp.Where,
 660        exp.With,
 661    )
 662
 663    # Expressions that should not have their comments generated in maybe_comment
 664    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 665        exp.Binary,
 666        exp.SetOperation,
 667    )
 668
 669    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 670    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 671        exp.Column,
 672        exp.Literal,
 673        exp.Neg,
 674        exp.Paren,
 675    )
 676
 677    PARAMETERIZABLE_TEXT_TYPES = {
 678        exp.DataType.Type.NVARCHAR,
 679        exp.DataType.Type.VARCHAR,
 680        exp.DataType.Type.CHAR,
 681        exp.DataType.Type.NCHAR,
 682    }
 683
 684    # Expressions that need to have all CTEs under them bubbled up to them
 685    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 686
 687    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 688
 689    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 690
 691    __slots__ = (
 692        "pretty",
 693        "identify",
 694        "normalize",
 695        "pad",
 696        "_indent",
 697        "normalize_functions",
 698        "unsupported_level",
 699        "max_unsupported",
 700        "leading_comma",
 701        "max_text_width",
 702        "comments",
 703        "dialect",
 704        "unsupported_messages",
 705        "_escaped_quote_end",
 706        "_escaped_identifier_end",
 707        "_next_name",
 708        "_identifier_start",
 709        "_identifier_end",
 710        "_quote_json_path_key_using_brackets",
 711    )
 712
 713    def __init__(
 714        self,
 715        pretty: t.Optional[bool] = None,
 716        identify: str | bool = False,
 717        normalize: bool = False,
 718        pad: int = 2,
 719        indent: int = 2,
 720        normalize_functions: t.Optional[str | bool] = None,
 721        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 722        max_unsupported: int = 3,
 723        leading_comma: bool = False,
 724        max_text_width: int = 80,
 725        comments: bool = True,
 726        dialect: DialectType = None,
 727    ):
 728        import sqlglot
 729        from sqlglot.dialects import Dialect
 730
 731        self.pretty = pretty if pretty is not None else sqlglot.pretty
 732        self.identify = identify
 733        self.normalize = normalize
 734        self.pad = pad
 735        self._indent = indent
 736        self.unsupported_level = unsupported_level
 737        self.max_unsupported = max_unsupported
 738        self.leading_comma = leading_comma
 739        self.max_text_width = max_text_width
 740        self.comments = comments
 741        self.dialect = Dialect.get_or_raise(dialect)
 742
 743        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 744        self.normalize_functions = (
 745            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 746        )
 747
 748        self.unsupported_messages: t.List[str] = []
 749        self._escaped_quote_end: str = (
 750            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 751        )
 752        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 753
 754        self._next_name = name_sequence("_t")
 755
 756        self._identifier_start = self.dialect.IDENTIFIER_START
 757        self._identifier_end = self.dialect.IDENTIFIER_END
 758
 759        self._quote_json_path_key_using_brackets = True
 760
 761    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 762        """
 763        Generates the SQL string corresponding to the given syntax tree.
 764
 765        Args:
 766            expression: The syntax tree.
 767            copy: Whether to copy the expression. The generator performs mutations so
 768                it is safer to copy.
 769
 770        Returns:
 771            The SQL string corresponding to `expression`.
 772        """
 773        if copy:
 774            expression = expression.copy()
 775
 776        expression = self.preprocess(expression)
 777
 778        self.unsupported_messages = []
 779        sql = self.sql(expression).strip()
 780
 781        if self.pretty:
 782            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 783
 784        if self.unsupported_level == ErrorLevel.IGNORE:
 785            return sql
 786
 787        if self.unsupported_level == ErrorLevel.WARN:
 788            for msg in self.unsupported_messages:
 789                logger.warning(msg)
 790        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 791            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 792
 793        return sql
 794
 795    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 796        """Apply generic preprocessing transformations to a given expression."""
 797        expression = self._move_ctes_to_top_level(expression)
 798
 799        if self.ENSURE_BOOLS:
 800            from sqlglot.transforms import ensure_bools
 801
 802            expression = ensure_bools(expression)
 803
 804        return expression
 805
 806    def _move_ctes_to_top_level(self, expression: E) -> E:
 807        if (
 808            not expression.parent
 809            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 810            and any(node.parent is not expression for node in expression.find_all(exp.With))
 811        ):
 812            from sqlglot.transforms import move_ctes_to_top_level
 813
 814            expression = move_ctes_to_top_level(expression)
 815        return expression
 816
 817    def unsupported(self, message: str) -> None:
 818        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 819            raise UnsupportedError(message)
 820        self.unsupported_messages.append(message)
 821
 822    def sep(self, sep: str = " ") -> str:
 823        return f"{sep.strip()}\n" if self.pretty else sep
 824
 825    def seg(self, sql: str, sep: str = " ") -> str:
 826        return f"{self.sep(sep)}{sql}"
 827
 828    def sanitize_comment(self, comment: str) -> str:
 829        comment = " " + comment if comment[0].strip() else comment
 830        comment = comment + " " if comment[-1].strip() else comment
 831
 832        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 833            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 834            comment = comment.replace("*/", "* /")
 835
 836        return comment
 837
 838    def maybe_comment(
 839        self,
 840        sql: str,
 841        expression: t.Optional[exp.Expression] = None,
 842        comments: t.Optional[t.List[str]] = None,
 843        separated: bool = False,
 844    ) -> str:
 845        comments = (
 846            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 847            if self.comments
 848            else None
 849        )
 850
 851        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 852            return sql
 853
 854        comments_sql = " ".join(
 855            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 856        )
 857
 858        if not comments_sql:
 859            return sql
 860
 861        comments_sql = self._replace_line_breaks(comments_sql)
 862
 863        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 864            return (
 865                f"{self.sep()}{comments_sql}{sql}"
 866                if not sql or sql[0].isspace()
 867                else f"{comments_sql}{self.sep()}{sql}"
 868            )
 869
 870        return f"{sql} {comments_sql}"
 871
 872    def wrap(self, expression: exp.Expression | str) -> str:
 873        this_sql = (
 874            self.sql(expression)
 875            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 876            else self.sql(expression, "this")
 877        )
 878        if not this_sql:
 879            return "()"
 880
 881        this_sql = self.indent(this_sql, level=1, pad=0)
 882        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 883
 884    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 885        original = self.identify
 886        self.identify = False
 887        result = func(*args, **kwargs)
 888        self.identify = original
 889        return result
 890
 891    def normalize_func(self, name: str) -> str:
 892        if self.normalize_functions == "upper" or self.normalize_functions is True:
 893            return name.upper()
 894        if self.normalize_functions == "lower":
 895            return name.lower()
 896        return name
 897
 898    def indent(
 899        self,
 900        sql: str,
 901        level: int = 0,
 902        pad: t.Optional[int] = None,
 903        skip_first: bool = False,
 904        skip_last: bool = False,
 905    ) -> str:
 906        if not self.pretty or not sql:
 907            return sql
 908
 909        pad = self.pad if pad is None else pad
 910        lines = sql.split("\n")
 911
 912        return "\n".join(
 913            (
 914                line
 915                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 916                else f"{' ' * (level * self._indent + pad)}{line}"
 917            )
 918            for i, line in enumerate(lines)
 919        )
 920
 921    def sql(
 922        self,
 923        expression: t.Optional[str | exp.Expression],
 924        key: t.Optional[str] = None,
 925        comment: bool = True,
 926    ) -> str:
 927        if not expression:
 928            return ""
 929
 930        if isinstance(expression, str):
 931            return expression
 932
 933        if key:
 934            value = expression.args.get(key)
 935            if value:
 936                return self.sql(value)
 937            return ""
 938
 939        transform = self.TRANSFORMS.get(expression.__class__)
 940
 941        if callable(transform):
 942            sql = transform(self, expression)
 943        elif isinstance(expression, exp.Expression):
 944            exp_handler_name = f"{expression.key}_sql"
 945
 946            if hasattr(self, exp_handler_name):
 947                sql = getattr(self, exp_handler_name)(expression)
 948            elif isinstance(expression, exp.Func):
 949                sql = self.function_fallback_sql(expression)
 950            elif isinstance(expression, exp.Property):
 951                sql = self.property_sql(expression)
 952            else:
 953                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 954        else:
 955            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 956
 957        return self.maybe_comment(sql, expression) if self.comments and comment else sql
 958
 959    def uncache_sql(self, expression: exp.Uncache) -> str:
 960        table = self.sql(expression, "this")
 961        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 962        return f"UNCACHE TABLE{exists_sql} {table}"
 963
 964    def cache_sql(self, expression: exp.Cache) -> str:
 965        lazy = " LAZY" if expression.args.get("lazy") else ""
 966        table = self.sql(expression, "this")
 967        options = expression.args.get("options")
 968        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 969        sql = self.sql(expression, "expression")
 970        sql = f" AS{self.sep()}{sql}" if sql else ""
 971        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
 972        return self.prepend_ctes(expression, sql)
 973
 974    def characterset_sql(self, expression: exp.CharacterSet) -> str:
 975        if isinstance(expression.parent, exp.Cast):
 976            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
 977        default = "DEFAULT " if expression.args.get("default") else ""
 978        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
 979
 980    def column_parts(self, expression: exp.Column) -> str:
 981        return ".".join(
 982            self.sql(part)
 983            for part in (
 984                expression.args.get("catalog"),
 985                expression.args.get("db"),
 986                expression.args.get("table"),
 987                expression.args.get("this"),
 988            )
 989            if part
 990        )
 991
 992    def column_sql(self, expression: exp.Column) -> str:
 993        join_mark = " (+)" if expression.args.get("join_mark") else ""
 994
 995        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 996            join_mark = ""
 997            self.unsupported("Outer join syntax using the (+) operator is not supported.")
 998
 999        return f"{self.column_parts(expression)}{join_mark}"
1000
1001    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1002        this = self.sql(expression, "this")
1003        this = f" {this}" if this else ""
1004        position = self.sql(expression, "position")
1005        return f"{position}{this}"
1006
1007    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1008        column = self.sql(expression, "this")
1009        kind = self.sql(expression, "kind")
1010        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1011        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1012        kind = f"{sep}{kind}" if kind else ""
1013        constraints = f" {constraints}" if constraints else ""
1014        position = self.sql(expression, "position")
1015        position = f" {position}" if position else ""
1016
1017        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1018            kind = ""
1019
1020        return f"{exists}{column}{kind}{constraints}{position}"
1021
1022    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1023        this = self.sql(expression, "this")
1024        kind_sql = self.sql(expression, "kind").strip()
1025        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1026
1027    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1028        this = self.sql(expression, "this")
1029        if expression.args.get("not_null"):
1030            persisted = " PERSISTED NOT NULL"
1031        elif expression.args.get("persisted"):
1032            persisted = " PERSISTED"
1033        else:
1034            persisted = ""
1035
1036        return f"AS {this}{persisted}"
1037
1038    def autoincrementcolumnconstraint_sql(self, _) -> str:
1039        return self.token_sql(TokenType.AUTO_INCREMENT)
1040
1041    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1042        if isinstance(expression.this, list):
1043            this = self.wrap(self.expressions(expression, key="this", flat=True))
1044        else:
1045            this = self.sql(expression, "this")
1046
1047        return f"COMPRESS {this}"
1048
1049    def generatedasidentitycolumnconstraint_sql(
1050        self, expression: exp.GeneratedAsIdentityColumnConstraint
1051    ) -> str:
1052        this = ""
1053        if expression.this is not None:
1054            on_null = " ON NULL" if expression.args.get("on_null") else ""
1055            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1056
1057        start = expression.args.get("start")
1058        start = f"START WITH {start}" if start else ""
1059        increment = expression.args.get("increment")
1060        increment = f" INCREMENT BY {increment}" if increment else ""
1061        minvalue = expression.args.get("minvalue")
1062        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1063        maxvalue = expression.args.get("maxvalue")
1064        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1065        cycle = expression.args.get("cycle")
1066        cycle_sql = ""
1067
1068        if cycle is not None:
1069            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1070            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1071
1072        sequence_opts = ""
1073        if start or increment or cycle_sql:
1074            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1075            sequence_opts = f" ({sequence_opts.strip()})"
1076
1077        expr = self.sql(expression, "expression")
1078        expr = f"({expr})" if expr else "IDENTITY"
1079
1080        return f"GENERATED{this} AS {expr}{sequence_opts}"
1081
1082    def generatedasrowcolumnconstraint_sql(
1083        self, expression: exp.GeneratedAsRowColumnConstraint
1084    ) -> str:
1085        start = "START" if expression.args.get("start") else "END"
1086        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1087        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1088
1089    def periodforsystemtimeconstraint_sql(
1090        self, expression: exp.PeriodForSystemTimeConstraint
1091    ) -> str:
1092        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1093
1094    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1095        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1096
1097    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1098        desc = expression.args.get("desc")
1099        if desc is not None:
1100            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1101        options = self.expressions(expression, key="options", flat=True, sep=" ")
1102        options = f" {options}" if options else ""
1103        return f"PRIMARY KEY{options}"
1104
1105    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1106        this = self.sql(expression, "this")
1107        this = f" {this}" if this else ""
1108        index_type = expression.args.get("index_type")
1109        index_type = f" USING {index_type}" if index_type else ""
1110        on_conflict = self.sql(expression, "on_conflict")
1111        on_conflict = f" {on_conflict}" if on_conflict else ""
1112        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1113        options = self.expressions(expression, key="options", flat=True, sep=" ")
1114        options = f" {options}" if options else ""
1115        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1116
1117    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1118        return self.sql(expression, "this")
1119
1120    def create_sql(self, expression: exp.Create) -> str:
1121        kind = self.sql(expression, "kind")
1122        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1123        properties = expression.args.get("properties")
1124        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1125
1126        this = self.createable_sql(expression, properties_locs)
1127
1128        properties_sql = ""
1129        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1130            exp.Properties.Location.POST_WITH
1131        ):
1132            properties_sql = self.sql(
1133                exp.Properties(
1134                    expressions=[
1135                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
1136                        *properties_locs[exp.Properties.Location.POST_WITH],
1137                    ]
1138                )
1139            )
1140
1141            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1142                properties_sql = self.sep() + properties_sql
1143            elif not self.pretty:
1144                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1145                properties_sql = f" {properties_sql}"
1146
1147        begin = " BEGIN" if expression.args.get("begin") else ""
1148        end = " END" if expression.args.get("end") else ""
1149
1150        expression_sql = self.sql(expression, "expression")
1151        if expression_sql:
1152            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1153
1154            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1155                postalias_props_sql = ""
1156                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1157                    postalias_props_sql = self.properties(
1158                        exp.Properties(
1159                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1160                        ),
1161                        wrapped=False,
1162                    )
1163                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1164                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1165
1166        postindex_props_sql = ""
1167        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1168            postindex_props_sql = self.properties(
1169                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1170                wrapped=False,
1171                prefix=" ",
1172            )
1173
1174        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1175        indexes = f" {indexes}" if indexes else ""
1176        index_sql = indexes + postindex_props_sql
1177
1178        replace = " OR REPLACE" if expression.args.get("replace") else ""
1179        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1180        unique = " UNIQUE" if expression.args.get("unique") else ""
1181
1182        clustered = expression.args.get("clustered")
1183        if clustered is None:
1184            clustered_sql = ""
1185        elif clustered:
1186            clustered_sql = " CLUSTERED COLUMNSTORE"
1187        else:
1188            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1189
1190        postcreate_props_sql = ""
1191        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1192            postcreate_props_sql = self.properties(
1193                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1194                sep=" ",
1195                prefix=" ",
1196                wrapped=False,
1197            )
1198
1199        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1200
1201        postexpression_props_sql = ""
1202        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1203            postexpression_props_sql = self.properties(
1204                exp.Properties(
1205                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1206                ),
1207                sep=" ",
1208                prefix=" ",
1209                wrapped=False,
1210            )
1211
1212        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1213        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1214        no_schema_binding = (
1215            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1216        )
1217
1218        clone = self.sql(expression, "clone")
1219        clone = f" {clone}" if clone else ""
1220
1221        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1222            properties_expression = f"{expression_sql}{properties_sql}"
1223        else:
1224            properties_expression = f"{properties_sql}{expression_sql}"
1225
1226        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1227        return self.prepend_ctes(expression, expression_sql)
1228
1229    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1230        start = self.sql(expression, "start")
1231        start = f"START WITH {start}" if start else ""
1232        increment = self.sql(expression, "increment")
1233        increment = f" INCREMENT BY {increment}" if increment else ""
1234        minvalue = self.sql(expression, "minvalue")
1235        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1236        maxvalue = self.sql(expression, "maxvalue")
1237        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1238        owned = self.sql(expression, "owned")
1239        owned = f" OWNED BY {owned}" if owned else ""
1240
1241        cache = expression.args.get("cache")
1242        if cache is None:
1243            cache_str = ""
1244        elif cache is True:
1245            cache_str = " CACHE"
1246        else:
1247            cache_str = f" CACHE {cache}"
1248
1249        options = self.expressions(expression, key="options", flat=True, sep=" ")
1250        options = f" {options}" if options else ""
1251
1252        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1253
1254    def clone_sql(self, expression: exp.Clone) -> str:
1255        this = self.sql(expression, "this")
1256        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1257        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1258        return f"{shallow}{keyword} {this}"
1259
1260    def describe_sql(self, expression: exp.Describe) -> str:
1261        style = expression.args.get("style")
1262        style = f" {style}" if style else ""
1263        partition = self.sql(expression, "partition")
1264        partition = f" {partition}" if partition else ""
1265        format = self.sql(expression, "format")
1266        format = f" {format}" if format else ""
1267
1268        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1269
1270    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1271        tag = self.sql(expression, "tag")
1272        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1273
1274    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1275        with_ = self.sql(expression, "with")
1276        if with_:
1277            sql = f"{with_}{self.sep()}{sql}"
1278        return sql
1279
1280    def with_sql(self, expression: exp.With) -> str:
1281        sql = self.expressions(expression, flat=True)
1282        recursive = (
1283            "RECURSIVE "
1284            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1285            else ""
1286        )
1287        search = self.sql(expression, "search")
1288        search = f" {search}" if search else ""
1289
1290        return f"WITH {recursive}{sql}{search}"
1291
1292    def cte_sql(self, expression: exp.CTE) -> str:
1293        alias = expression.args.get("alias")
1294        if alias:
1295            alias.add_comments(expression.pop_comments())
1296
1297        alias_sql = self.sql(expression, "alias")
1298
1299        materialized = expression.args.get("materialized")
1300        if materialized is False:
1301            materialized = "NOT MATERIALIZED "
1302        elif materialized:
1303            materialized = "MATERIALIZED "
1304
1305        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1306
1307    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1308        alias = self.sql(expression, "this")
1309        columns = self.expressions(expression, key="columns", flat=True)
1310        columns = f"({columns})" if columns else ""
1311
1312        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1313            columns = ""
1314            self.unsupported("Named columns are not supported in table alias.")
1315
1316        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1317            alias = self._next_name()
1318
1319        return f"{alias}{columns}"
1320
1321    def bitstring_sql(self, expression: exp.BitString) -> str:
1322        this = self.sql(expression, "this")
1323        if self.dialect.BIT_START:
1324            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1325        return f"{int(this, 2)}"
1326
1327    def hexstring_sql(
1328        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1329    ) -> str:
1330        this = self.sql(expression, "this")
1331        is_integer_type = expression.args.get("is_integer")
1332
1333        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1334            not self.dialect.HEX_START and not binary_function_repr
1335        ):
1336            # Integer representation will be returned if:
1337            # - The read dialect treats the hex value as integer literal but not the write
1338            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1339            return f"{int(this, 16)}"
1340
1341        if not is_integer_type:
1342            # Read dialect treats the hex value as BINARY/BLOB
1343            if binary_function_repr:
1344                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1345                return self.func(binary_function_repr, exp.Literal.string(this))
1346            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1347                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1348                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1349
1350        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1351
1352    def bytestring_sql(self, expression: exp.ByteString) -> str:
1353        this = self.sql(expression, "this")
1354        if self.dialect.BYTE_START:
1355            return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1356        return this
1357
1358    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1359        this = self.sql(expression, "this")
1360        escape = expression.args.get("escape")
1361
1362        if self.dialect.UNICODE_START:
1363            escape_substitute = r"\\\1"
1364            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1365        else:
1366            escape_substitute = r"\\u\1"
1367            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1368
1369        if escape:
1370            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1371            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1372        else:
1373            escape_pattern = ESCAPED_UNICODE_RE
1374            escape_sql = ""
1375
1376        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1377            this = escape_pattern.sub(escape_substitute, this)
1378
1379        return f"{left_quote}{this}{right_quote}{escape_sql}"
1380
1381    def rawstring_sql(self, expression: exp.RawString) -> str:
1382        string = expression.this
1383        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1384            string = string.replace("\\", "\\\\")
1385
1386        string = self.escape_str(string, escape_backslash=False)
1387        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1388
1389    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1390        this = self.sql(expression, "this")
1391        specifier = self.sql(expression, "expression")
1392        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1393        return f"{this}{specifier}"
1394
1395    def datatype_sql(self, expression: exp.DataType) -> str:
1396        nested = ""
1397        values = ""
1398        interior = self.expressions(expression, flat=True)
1399
1400        type_value = expression.this
1401        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1402            type_sql = self.sql(expression, "kind")
1403        else:
1404            type_sql = (
1405                self.TYPE_MAPPING.get(type_value, type_value.value)
1406                if isinstance(type_value, exp.DataType.Type)
1407                else type_value
1408            )
1409
1410        if interior:
1411            if expression.args.get("nested"):
1412                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1413                if expression.args.get("values") is not None:
1414                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1415                    values = self.expressions(expression, key="values", flat=True)
1416                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1417            elif type_value == exp.DataType.Type.INTERVAL:
1418                nested = f" {interior}"
1419            else:
1420                nested = f"({interior})"
1421
1422        type_sql = f"{type_sql}{nested}{values}"
1423        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1424            exp.DataType.Type.TIMETZ,
1425            exp.DataType.Type.TIMESTAMPTZ,
1426        ):
1427            type_sql = f"{type_sql} WITH TIME ZONE"
1428
1429        return type_sql
1430
1431    def directory_sql(self, expression: exp.Directory) -> str:
1432        local = "LOCAL " if expression.args.get("local") else ""
1433        row_format = self.sql(expression, "row_format")
1434        row_format = f" {row_format}" if row_format else ""
1435        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1436
1437    def delete_sql(self, expression: exp.Delete) -> str:
1438        this = self.sql(expression, "this")
1439        this = f" FROM {this}" if this else ""
1440        using = self.sql(expression, "using")
1441        using = f" USING {using}" if using else ""
1442        cluster = self.sql(expression, "cluster")
1443        cluster = f" {cluster}" if cluster else ""
1444        where = self.sql(expression, "where")
1445        returning = self.sql(expression, "returning")
1446        limit = self.sql(expression, "limit")
1447        tables = self.expressions(expression, key="tables")
1448        tables = f" {tables}" if tables else ""
1449        if self.RETURNING_END:
1450            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1451        else:
1452            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1453        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1454
1455    def drop_sql(self, expression: exp.Drop) -> str:
1456        this = self.sql(expression, "this")
1457        expressions = self.expressions(expression, flat=True)
1458        expressions = f" ({expressions})" if expressions else ""
1459        kind = expression.args["kind"]
1460        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1461        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1462        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1463        on_cluster = self.sql(expression, "cluster")
1464        on_cluster = f" {on_cluster}" if on_cluster else ""
1465        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1466        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1467        cascade = " CASCADE" if expression.args.get("cascade") else ""
1468        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1469        purge = " PURGE" if expression.args.get("purge") else ""
1470        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1471
1472    def set_operation(self, expression: exp.SetOperation) -> str:
1473        op_type = type(expression)
1474        op_name = op_type.key.upper()
1475
1476        distinct = expression.args.get("distinct")
1477        if (
1478            distinct is False
1479            and op_type in (exp.Except, exp.Intersect)
1480            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1481        ):
1482            self.unsupported(f"{op_name} ALL is not supported")
1483
1484        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1485
1486        if distinct is None:
1487            distinct = default_distinct
1488            if distinct is None:
1489                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1490
1491        if distinct is default_distinct:
1492            distinct_or_all = ""
1493        else:
1494            distinct_or_all = " DISTINCT" if distinct else " ALL"
1495
1496        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1497        side_kind = f"{side_kind} " if side_kind else ""
1498
1499        by_name = " BY NAME" if expression.args.get("by_name") else ""
1500        on = self.expressions(expression, key="on", flat=True)
1501        on = f" ON ({on})" if on else ""
1502
1503        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1504
1505    def set_operations(self, expression: exp.SetOperation) -> str:
1506        if not self.SET_OP_MODIFIERS:
1507            limit = expression.args.get("limit")
1508            order = expression.args.get("order")
1509
1510            if limit or order:
1511                select = self._move_ctes_to_top_level(
1512                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1513                )
1514
1515                if limit:
1516                    select = select.limit(limit.pop(), copy=False)
1517                if order:
1518                    select = select.order_by(order.pop(), copy=False)
1519                return self.sql(select)
1520
1521        sqls: t.List[str] = []
1522        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1523
1524        while stack:
1525            node = stack.pop()
1526
1527            if isinstance(node, exp.SetOperation):
1528                stack.append(node.expression)
1529                stack.append(
1530                    self.maybe_comment(
1531                        self.set_operation(node), comments=node.comments, separated=True
1532                    )
1533                )
1534                stack.append(node.this)
1535            else:
1536                sqls.append(self.sql(node))
1537
1538        this = self.sep().join(sqls)
1539        this = self.query_modifiers(expression, this)
1540        return self.prepend_ctes(expression, this)
1541
1542    def fetch_sql(self, expression: exp.Fetch) -> str:
1543        direction = expression.args.get("direction")
1544        direction = f" {direction}" if direction else ""
1545        count = self.sql(expression, "count")
1546        count = f" {count}" if count else ""
1547        limit_options = self.sql(expression, "limit_options")
1548        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1549        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1550
1551    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1552        percent = " PERCENT" if expression.args.get("percent") else ""
1553        rows = " ROWS" if expression.args.get("rows") else ""
1554        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1555        if not with_ties and rows:
1556            with_ties = " ONLY"
1557        return f"{percent}{rows}{with_ties}"
1558
1559    def filter_sql(self, expression: exp.Filter) -> str:
1560        if self.AGGREGATE_FILTER_SUPPORTED:
1561            this = self.sql(expression, "this")
1562            where = self.sql(expression, "expression").strip()
1563            return f"{this} FILTER({where})"
1564
1565        agg = expression.this
1566        agg_arg = agg.this
1567        cond = expression.expression.this
1568        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1569        return self.sql(agg)
1570
1571    def hint_sql(self, expression: exp.Hint) -> str:
1572        if not self.QUERY_HINTS:
1573            self.unsupported("Hints are not supported")
1574            return ""
1575
1576        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1577
1578    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1579        using = self.sql(expression, "using")
1580        using = f" USING {using}" if using else ""
1581        columns = self.expressions(expression, key="columns", flat=True)
1582        columns = f"({columns})" if columns else ""
1583        partition_by = self.expressions(expression, key="partition_by", flat=True)
1584        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1585        where = self.sql(expression, "where")
1586        include = self.expressions(expression, key="include", flat=True)
1587        if include:
1588            include = f" INCLUDE ({include})"
1589        with_storage = self.expressions(expression, key="with_storage", flat=True)
1590        with_storage = f" WITH ({with_storage})" if with_storage else ""
1591        tablespace = self.sql(expression, "tablespace")
1592        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1593        on = self.sql(expression, "on")
1594        on = f" ON {on}" if on else ""
1595
1596        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1597
1598    def index_sql(self, expression: exp.Index) -> str:
1599        unique = "UNIQUE " if expression.args.get("unique") else ""
1600        primary = "PRIMARY " if expression.args.get("primary") else ""
1601        amp = "AMP " if expression.args.get("amp") else ""
1602        name = self.sql(expression, "this")
1603        name = f"{name} " if name else ""
1604        table = self.sql(expression, "table")
1605        table = f"{self.INDEX_ON} {table}" if table else ""
1606
1607        index = "INDEX " if not table else ""
1608
1609        params = self.sql(expression, "params")
1610        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1611
1612    def identifier_sql(self, expression: exp.Identifier) -> str:
1613        text = expression.name
1614        lower = text.lower()
1615        text = lower if self.normalize and not expression.quoted else text
1616        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1617        if (
1618            expression.quoted
1619            or self.dialect.can_identify(text, self.identify)
1620            or lower in self.RESERVED_KEYWORDS
1621            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1622        ):
1623            text = f"{self._identifier_start}{text}{self._identifier_end}"
1624        return text
1625
1626    def hex_sql(self, expression: exp.Hex) -> str:
1627        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1628        if self.dialect.HEX_LOWERCASE:
1629            text = self.func("LOWER", text)
1630
1631        return text
1632
1633    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1634        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1635        if not self.dialect.HEX_LOWERCASE:
1636            text = self.func("LOWER", text)
1637        return text
1638
1639    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1640        input_format = self.sql(expression, "input_format")
1641        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1642        output_format = self.sql(expression, "output_format")
1643        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1644        return self.sep().join((input_format, output_format))
1645
1646    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1647        string = self.sql(exp.Literal.string(expression.name))
1648        return f"{prefix}{string}"
1649
1650    def partition_sql(self, expression: exp.Partition) -> str:
1651        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1652        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1653
1654    def properties_sql(self, expression: exp.Properties) -> str:
1655        root_properties = []
1656        with_properties = []
1657
1658        for p in expression.expressions:
1659            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1660            if p_loc == exp.Properties.Location.POST_WITH:
1661                with_properties.append(p)
1662            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1663                root_properties.append(p)
1664
1665        root_props = self.root_properties(exp.Properties(expressions=root_properties))
1666        with_props = self.with_properties(exp.Properties(expressions=with_properties))
1667
1668        if root_props and with_props and not self.pretty:
1669            with_props = " " + with_props
1670
1671        return root_props + with_props
1672
1673    def root_properties(self, properties: exp.Properties) -> str:
1674        if properties.expressions:
1675            return self.expressions(properties, indent=False, sep=" ")
1676        return ""
1677
1678    def properties(
1679        self,
1680        properties: exp.Properties,
1681        prefix: str = "",
1682        sep: str = ", ",
1683        suffix: str = "",
1684        wrapped: bool = True,
1685    ) -> str:
1686        if properties.expressions:
1687            expressions = self.expressions(properties, sep=sep, indent=False)
1688            if expressions:
1689                expressions = self.wrap(expressions) if wrapped else expressions
1690                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1691        return ""
1692
1693    def with_properties(self, properties: exp.Properties) -> str:
1694        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1695
1696    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1697        properties_locs = defaultdict(list)
1698        for p in properties.expressions:
1699            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1700            if p_loc != exp.Properties.Location.UNSUPPORTED:
1701                properties_locs[p_loc].append(p)
1702            else:
1703                self.unsupported(f"Unsupported property {p.key}")
1704
1705        return properties_locs
1706
1707    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1708        if isinstance(expression.this, exp.Dot):
1709            return self.sql(expression, "this")
1710        return f"'{expression.name}'" if string_key else expression.name
1711
1712    def property_sql(self, expression: exp.Property) -> str:
1713        property_cls = expression.__class__
1714        if property_cls == exp.Property:
1715            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1716
1717        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1718        if not property_name:
1719            self.unsupported(f"Unsupported property {expression.key}")
1720
1721        return f"{property_name}={self.sql(expression, 'this')}"
1722
1723    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1724        if self.SUPPORTS_CREATE_TABLE_LIKE:
1725            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1726            options = f" {options}" if options else ""
1727
1728            like = f"LIKE {self.sql(expression, 'this')}{options}"
1729            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1730                like = f"({like})"
1731
1732            return like
1733
1734        if expression.expressions:
1735            self.unsupported("Transpilation of LIKE property options is unsupported")
1736
1737        select = exp.select("*").from_(expression.this).limit(0)
1738        return f"AS {self.sql(select)}"
1739
1740    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1741        no = "NO " if expression.args.get("no") else ""
1742        protection = " PROTECTION" if expression.args.get("protection") else ""
1743        return f"{no}FALLBACK{protection}"
1744
1745    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1746        no = "NO " if expression.args.get("no") else ""
1747        local = expression.args.get("local")
1748        local = f"{local} " if local else ""
1749        dual = "DUAL " if expression.args.get("dual") else ""
1750        before = "BEFORE " if expression.args.get("before") else ""
1751        after = "AFTER " if expression.args.get("after") else ""
1752        return f"{no}{local}{dual}{before}{after}JOURNAL"
1753
1754    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1755        freespace = self.sql(expression, "this")
1756        percent = " PERCENT" if expression.args.get("percent") else ""
1757        return f"FREESPACE={freespace}{percent}"
1758
1759    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1760        if expression.args.get("default"):
1761            property = "DEFAULT"
1762        elif expression.args.get("on"):
1763            property = "ON"
1764        else:
1765            property = "OFF"
1766        return f"CHECKSUM={property}"
1767
1768    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1769        if expression.args.get("no"):
1770            return "NO MERGEBLOCKRATIO"
1771        if expression.args.get("default"):
1772            return "DEFAULT MERGEBLOCKRATIO"
1773
1774        percent = " PERCENT" if expression.args.get("percent") else ""
1775        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1776
1777    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1778        default = expression.args.get("default")
1779        minimum = expression.args.get("minimum")
1780        maximum = expression.args.get("maximum")
1781        if default or minimum or maximum:
1782            if default:
1783                prop = "DEFAULT"
1784            elif minimum:
1785                prop = "MINIMUM"
1786            else:
1787                prop = "MAXIMUM"
1788            return f"{prop} DATABLOCKSIZE"
1789        units = expression.args.get("units")
1790        units = f" {units}" if units else ""
1791        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1792
1793    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1794        autotemp = expression.args.get("autotemp")
1795        always = expression.args.get("always")
1796        default = expression.args.get("default")
1797        manual = expression.args.get("manual")
1798        never = expression.args.get("never")
1799
1800        if autotemp is not None:
1801            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1802        elif always:
1803            prop = "ALWAYS"
1804        elif default:
1805            prop = "DEFAULT"
1806        elif manual:
1807            prop = "MANUAL"
1808        elif never:
1809            prop = "NEVER"
1810        return f"BLOCKCOMPRESSION={prop}"
1811
1812    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1813        no = expression.args.get("no")
1814        no = " NO" if no else ""
1815        concurrent = expression.args.get("concurrent")
1816        concurrent = " CONCURRENT" if concurrent else ""
1817        target = self.sql(expression, "target")
1818        target = f" {target}" if target else ""
1819        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1820
1821    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1822        if isinstance(expression.this, list):
1823            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1824        if expression.this:
1825            modulus = self.sql(expression, "this")
1826            remainder = self.sql(expression, "expression")
1827            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1828
1829        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1830        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1831        return f"FROM ({from_expressions}) TO ({to_expressions})"
1832
1833    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1834        this = self.sql(expression, "this")
1835
1836        for_values_or_default = expression.expression
1837        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1838            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1839        else:
1840            for_values_or_default = " DEFAULT"
1841
1842        return f"PARTITION OF {this}{for_values_or_default}"
1843
1844    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1845        kind = expression.args.get("kind")
1846        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1847        for_or_in = expression.args.get("for_or_in")
1848        for_or_in = f" {for_or_in}" if for_or_in else ""
1849        lock_type = expression.args.get("lock_type")
1850        override = " OVERRIDE" if expression.args.get("override") else ""
1851        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1852
1853    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1854        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1855        statistics = expression.args.get("statistics")
1856        statistics_sql = ""
1857        if statistics is not None:
1858            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1859        return f"{data_sql}{statistics_sql}"
1860
1861    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1862        this = self.sql(expression, "this")
1863        this = f"HISTORY_TABLE={this}" if this else ""
1864        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1865        data_consistency = (
1866            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1867        )
1868        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1869        retention_period = (
1870            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1871        )
1872
1873        if this:
1874            on_sql = self.func("ON", this, data_consistency, retention_period)
1875        else:
1876            on_sql = "ON" if expression.args.get("on") else "OFF"
1877
1878        sql = f"SYSTEM_VERSIONING={on_sql}"
1879
1880        return f"WITH({sql})" if expression.args.get("with") else sql
1881
1882    def insert_sql(self, expression: exp.Insert) -> str:
1883        hint = self.sql(expression, "hint")
1884        overwrite = expression.args.get("overwrite")
1885
1886        if isinstance(expression.this, exp.Directory):
1887            this = " OVERWRITE" if overwrite else " INTO"
1888        else:
1889            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1890
1891        stored = self.sql(expression, "stored")
1892        stored = f" {stored}" if stored else ""
1893        alternative = expression.args.get("alternative")
1894        alternative = f" OR {alternative}" if alternative else ""
1895        ignore = " IGNORE" if expression.args.get("ignore") else ""
1896        is_function = expression.args.get("is_function")
1897        if is_function:
1898            this = f"{this} FUNCTION"
1899        this = f"{this} {self.sql(expression, 'this')}"
1900
1901        exists = " IF EXISTS" if expression.args.get("exists") else ""
1902        where = self.sql(expression, "where")
1903        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1904        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1905        on_conflict = self.sql(expression, "conflict")
1906        on_conflict = f" {on_conflict}" if on_conflict else ""
1907        by_name = " BY NAME" if expression.args.get("by_name") else ""
1908        returning = self.sql(expression, "returning")
1909
1910        if self.RETURNING_END:
1911            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1912        else:
1913            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1914
1915        partition_by = self.sql(expression, "partition")
1916        partition_by = f" {partition_by}" if partition_by else ""
1917        settings = self.sql(expression, "settings")
1918        settings = f" {settings}" if settings else ""
1919
1920        source = self.sql(expression, "source")
1921        source = f"TABLE {source}" if source else ""
1922
1923        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1924        return self.prepend_ctes(expression, sql)
1925
1926    def introducer_sql(self, expression: exp.Introducer) -> str:
1927        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1928
1929    def kill_sql(self, expression: exp.Kill) -> str:
1930        kind = self.sql(expression, "kind")
1931        kind = f" {kind}" if kind else ""
1932        this = self.sql(expression, "this")
1933        this = f" {this}" if this else ""
1934        return f"KILL{kind}{this}"
1935
1936    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1937        return expression.name
1938
1939    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1940        return expression.name
1941
1942    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1943        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1944
1945        constraint = self.sql(expression, "constraint")
1946        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1947
1948        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1949        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1950        action = self.sql(expression, "action")
1951
1952        expressions = self.expressions(expression, flat=True)
1953        if expressions:
1954            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1955            expressions = f" {set_keyword}{expressions}"
1956
1957        where = self.sql(expression, "where")
1958        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
1959
1960    def returning_sql(self, expression: exp.Returning) -> str:
1961        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
1962
1963    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1964        fields = self.sql(expression, "fields")
1965        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1966        escaped = self.sql(expression, "escaped")
1967        escaped = f" ESCAPED BY {escaped}" if escaped else ""
1968        items = self.sql(expression, "collection_items")
1969        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1970        keys = self.sql(expression, "map_keys")
1971        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1972        lines = self.sql(expression, "lines")
1973        lines = f" LINES TERMINATED BY {lines}" if lines else ""
1974        null = self.sql(expression, "null")
1975        null = f" NULL DEFINED AS {null}" if null else ""
1976        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1977
1978    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1979        return f"WITH ({self.expressions(expression, flat=True)})"
1980
1981    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1982        this = f"{self.sql(expression, 'this')} INDEX"
1983        target = self.sql(expression, "target")
1984        target = f" FOR {target}" if target else ""
1985        return f"{this}{target} ({self.expressions(expression, flat=True)})"
1986
1987    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
1988        this = self.sql(expression, "this")
1989        kind = self.sql(expression, "kind")
1990        expr = self.sql(expression, "expression")
1991        return f"{this} ({kind} => {expr})"
1992
1993    def table_parts(self, expression: exp.Table) -> str:
1994        return ".".join(
1995            self.sql(part)
1996            for part in (
1997                expression.args.get("catalog"),
1998                expression.args.get("db"),
1999                expression.args.get("this"),
2000            )
2001            if part is not None
2002        )
2003
2004    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2005        table = self.table_parts(expression)
2006        only = "ONLY " if expression.args.get("only") else ""
2007        partition = self.sql(expression, "partition")
2008        partition = f" {partition}" if partition else ""
2009        version = self.sql(expression, "version")
2010        version = f" {version}" if version else ""
2011        alias = self.sql(expression, "alias")
2012        alias = f"{sep}{alias}" if alias else ""
2013
2014        sample = self.sql(expression, "sample")
2015        if self.dialect.ALIAS_POST_TABLESAMPLE:
2016            sample_pre_alias = sample
2017            sample_post_alias = ""
2018        else:
2019            sample_pre_alias = ""
2020            sample_post_alias = sample
2021
2022        hints = self.expressions(expression, key="hints", sep=" ")
2023        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2024        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2025        joins = self.indent(
2026            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2027        )
2028        laterals = self.expressions(expression, key="laterals", sep="")
2029
2030        file_format = self.sql(expression, "format")
2031        if file_format:
2032            pattern = self.sql(expression, "pattern")
2033            pattern = f", PATTERN => {pattern}" if pattern else ""
2034            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2035
2036        ordinality = expression.args.get("ordinality") or ""
2037        if ordinality:
2038            ordinality = f" WITH ORDINALITY{alias}"
2039            alias = ""
2040
2041        when = self.sql(expression, "when")
2042        if when:
2043            table = f"{table} {when}"
2044
2045        changes = self.sql(expression, "changes")
2046        changes = f" {changes}" if changes else ""
2047
2048        rows_from = self.expressions(expression, key="rows_from")
2049        if rows_from:
2050            table = f"ROWS FROM {self.wrap(rows_from)}"
2051
2052        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2053
2054    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2055        table = self.func("TABLE", expression.this)
2056        alias = self.sql(expression, "alias")
2057        alias = f" AS {alias}" if alias else ""
2058        sample = self.sql(expression, "sample")
2059        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2060        joins = self.indent(
2061            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2062        )
2063        return f"{table}{alias}{pivots}{sample}{joins}"
2064
2065    def tablesample_sql(
2066        self,
2067        expression: exp.TableSample,
2068        tablesample_keyword: t.Optional[str] = None,
2069    ) -> str:
2070        method = self.sql(expression, "method")
2071        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2072        numerator = self.sql(expression, "bucket_numerator")
2073        denominator = self.sql(expression, "bucket_denominator")
2074        field = self.sql(expression, "bucket_field")
2075        field = f" ON {field}" if field else ""
2076        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2077        seed = self.sql(expression, "seed")
2078        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2079
2080        size = self.sql(expression, "size")
2081        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2082            size = f"{size} ROWS"
2083
2084        percent = self.sql(expression, "percent")
2085        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2086            percent = f"{percent} PERCENT"
2087
2088        expr = f"{bucket}{percent}{size}"
2089        if self.TABLESAMPLE_REQUIRES_PARENS:
2090            expr = f"({expr})"
2091
2092        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2093
2094    def pivot_sql(self, expression: exp.Pivot) -> str:
2095        expressions = self.expressions(expression, flat=True)
2096        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2097
2098        group = self.sql(expression, "group")
2099
2100        if expression.this:
2101            this = self.sql(expression, "this")
2102            if not expressions:
2103                return f"UNPIVOT {this}"
2104
2105            on = f"{self.seg('ON')} {expressions}"
2106            into = self.sql(expression, "into")
2107            into = f"{self.seg('INTO')} {into}" if into else ""
2108            using = self.expressions(expression, key="using", flat=True)
2109            using = f"{self.seg('USING')} {using}" if using else ""
2110            return f"{direction} {this}{on}{into}{using}{group}"
2111
2112        alias = self.sql(expression, "alias")
2113        alias = f" AS {alias}" if alias else ""
2114
2115        fields = self.expressions(
2116            expression,
2117            "fields",
2118            sep=" ",
2119            dynamic=True,
2120            new_line=True,
2121            skip_first=True,
2122            skip_last=True,
2123        )
2124
2125        include_nulls = expression.args.get("include_nulls")
2126        if include_nulls is not None:
2127            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2128        else:
2129            nulls = ""
2130
2131        default_on_null = self.sql(expression, "default_on_null")
2132        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2133        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2134
2135    def version_sql(self, expression: exp.Version) -> str:
2136        this = f"FOR {expression.name}"
2137        kind = expression.text("kind")
2138        expr = self.sql(expression, "expression")
2139        return f"{this} {kind} {expr}"
2140
2141    def tuple_sql(self, expression: exp.Tuple) -> str:
2142        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2143
2144    def update_sql(self, expression: exp.Update) -> str:
2145        this = self.sql(expression, "this")
2146        set_sql = self.expressions(expression, flat=True)
2147        from_sql = self.sql(expression, "from")
2148        where_sql = self.sql(expression, "where")
2149        returning = self.sql(expression, "returning")
2150        order = self.sql(expression, "order")
2151        limit = self.sql(expression, "limit")
2152        if self.RETURNING_END:
2153            expression_sql = f"{from_sql}{where_sql}{returning}"
2154        else:
2155            expression_sql = f"{returning}{from_sql}{where_sql}"
2156        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2157        return self.prepend_ctes(expression, sql)
2158
2159    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2160        values_as_table = values_as_table and self.VALUES_AS_TABLE
2161
2162        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2163        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2164            args = self.expressions(expression)
2165            alias = self.sql(expression, "alias")
2166            values = f"VALUES{self.seg('')}{args}"
2167            values = (
2168                f"({values})"
2169                if self.WRAP_DERIVED_VALUES
2170                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2171                else values
2172            )
2173            return f"{values} AS {alias}" if alias else values
2174
2175        # Converts `VALUES...` expression into a series of select unions.
2176        alias_node = expression.args.get("alias")
2177        column_names = alias_node and alias_node.columns
2178
2179        selects: t.List[exp.Query] = []
2180
2181        for i, tup in enumerate(expression.expressions):
2182            row = tup.expressions
2183
2184            if i == 0 and column_names:
2185                row = [
2186                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2187                ]
2188
2189            selects.append(exp.Select(expressions=row))
2190
2191        if self.pretty:
2192            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2193            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2194            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2195            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2196            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2197
2198        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2199        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2200        return f"({unions}){alias}"
2201
2202    def var_sql(self, expression: exp.Var) -> str:
2203        return self.sql(expression, "this")
2204
2205    @unsupported_args("expressions")
2206    def into_sql(self, expression: exp.Into) -> str:
2207        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2208        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2209        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2210
2211    def from_sql(self, expression: exp.From) -> str:
2212        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2213
2214    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2215        grouping_sets = self.expressions(expression, indent=False)
2216        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2217
2218    def rollup_sql(self, expression: exp.Rollup) -> str:
2219        expressions = self.expressions(expression, indent=False)
2220        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2221
2222    def cube_sql(self, expression: exp.Cube) -> str:
2223        expressions = self.expressions(expression, indent=False)
2224        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2225
2226    def group_sql(self, expression: exp.Group) -> str:
2227        group_by_all = expression.args.get("all")
2228        if group_by_all is True:
2229            modifier = " ALL"
2230        elif group_by_all is False:
2231            modifier = " DISTINCT"
2232        else:
2233            modifier = ""
2234
2235        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2236
2237        grouping_sets = self.expressions(expression, key="grouping_sets")
2238        cube = self.expressions(expression, key="cube")
2239        rollup = self.expressions(expression, key="rollup")
2240
2241        groupings = csv(
2242            self.seg(grouping_sets) if grouping_sets else "",
2243            self.seg(cube) if cube else "",
2244            self.seg(rollup) if rollup else "",
2245            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2246            sep=self.GROUPINGS_SEP,
2247        )
2248
2249        if (
2250            expression.expressions
2251            and groupings
2252            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2253        ):
2254            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2255
2256        return f"{group_by}{groupings}"
2257
2258    def having_sql(self, expression: exp.Having) -> str:
2259        this = self.indent(self.sql(expression, "this"))
2260        return f"{self.seg('HAVING')}{self.sep()}{this}"
2261
2262    def connect_sql(self, expression: exp.Connect) -> str:
2263        start = self.sql(expression, "start")
2264        start = self.seg(f"START WITH {start}") if start else ""
2265        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2266        connect = self.sql(expression, "connect")
2267        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2268        return start + connect
2269
2270    def prior_sql(self, expression: exp.Prior) -> str:
2271        return f"PRIOR {self.sql(expression, 'this')}"
2272
2273    def join_sql(self, expression: exp.Join) -> str:
2274        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2275            side = None
2276        else:
2277            side = expression.side
2278
2279        op_sql = " ".join(
2280            op
2281            for op in (
2282                expression.method,
2283                "GLOBAL" if expression.args.get("global") else None,
2284                side,
2285                expression.kind,
2286                expression.hint if self.JOIN_HINTS else None,
2287            )
2288            if op
2289        )
2290        match_cond = self.sql(expression, "match_condition")
2291        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2292        on_sql = self.sql(expression, "on")
2293        using = expression.args.get("using")
2294
2295        if not on_sql and using:
2296            on_sql = csv(*(self.sql(column) for column in using))
2297
2298        this = expression.this
2299        this_sql = self.sql(this)
2300
2301        exprs = self.expressions(expression)
2302        if exprs:
2303            this_sql = f"{this_sql},{self.seg(exprs)}"
2304
2305        if on_sql:
2306            on_sql = self.indent(on_sql, skip_first=True)
2307            space = self.seg(" " * self.pad) if self.pretty else " "
2308            if using:
2309                on_sql = f"{space}USING ({on_sql})"
2310            else:
2311                on_sql = f"{space}ON {on_sql}"
2312        elif not op_sql:
2313            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2314                return f" {this_sql}"
2315
2316            return f", {this_sql}"
2317
2318        if op_sql != "STRAIGHT_JOIN":
2319            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2320
2321        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2322        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2323
2324    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2325        args = self.expressions(expression, flat=True)
2326        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2327        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2328
2329    def lateral_op(self, expression: exp.Lateral) -> str:
2330        cross_apply = expression.args.get("cross_apply")
2331
2332        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2333        if cross_apply is True:
2334            op = "INNER JOIN "
2335        elif cross_apply is False:
2336            op = "LEFT JOIN "
2337        else:
2338            op = ""
2339
2340        return f"{op}LATERAL"
2341
2342    def lateral_sql(self, expression: exp.Lateral) -> str:
2343        this = self.sql(expression, "this")
2344
2345        if expression.args.get("view"):
2346            alias = expression.args["alias"]
2347            columns = self.expressions(alias, key="columns", flat=True)
2348            table = f" {alias.name}" if alias.name else ""
2349            columns = f" AS {columns}" if columns else ""
2350            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2351            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2352
2353        alias = self.sql(expression, "alias")
2354        alias = f" AS {alias}" if alias else ""
2355
2356        ordinality = expression.args.get("ordinality") or ""
2357        if ordinality:
2358            ordinality = f" WITH ORDINALITY{alias}"
2359            alias = ""
2360
2361        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2362
2363    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2364        this = self.sql(expression, "this")
2365
2366        args = [
2367            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2368            for e in (expression.args.get(k) for k in ("offset", "expression"))
2369            if e
2370        ]
2371
2372        args_sql = ", ".join(self.sql(e) for e in args)
2373        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2374        expressions = self.expressions(expression, flat=True)
2375        limit_options = self.sql(expression, "limit_options")
2376        expressions = f" BY {expressions}" if expressions else ""
2377
2378        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2379
2380    def offset_sql(self, expression: exp.Offset) -> str:
2381        this = self.sql(expression, "this")
2382        value = expression.expression
2383        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2384        expressions = self.expressions(expression, flat=True)
2385        expressions = f" BY {expressions}" if expressions else ""
2386        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2387
2388    def setitem_sql(self, expression: exp.SetItem) -> str:
2389        kind = self.sql(expression, "kind")
2390        kind = f"{kind} " if kind else ""
2391        this = self.sql(expression, "this")
2392        expressions = self.expressions(expression)
2393        collate = self.sql(expression, "collate")
2394        collate = f" COLLATE {collate}" if collate else ""
2395        global_ = "GLOBAL " if expression.args.get("global") else ""
2396        return f"{global_}{kind}{this}{expressions}{collate}"
2397
2398    def set_sql(self, expression: exp.Set) -> str:
2399        expressions = f" {self.expressions(expression, flat=True)}"
2400        tag = " TAG" if expression.args.get("tag") else ""
2401        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2402
2403    def pragma_sql(self, expression: exp.Pragma) -> str:
2404        return f"PRAGMA {self.sql(expression, 'this')}"
2405
2406    def lock_sql(self, expression: exp.Lock) -> str:
2407        if not self.LOCKING_READS_SUPPORTED:
2408            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2409            return ""
2410
2411        update = expression.args["update"]
2412        key = expression.args.get("key")
2413        if update:
2414            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2415        else:
2416            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2417        expressions = self.expressions(expression, flat=True)
2418        expressions = f" OF {expressions}" if expressions else ""
2419        wait = expression.args.get("wait")
2420
2421        if wait is not None:
2422            if isinstance(wait, exp.Literal):
2423                wait = f" WAIT {self.sql(wait)}"
2424            else:
2425                wait = " NOWAIT" if wait else " SKIP LOCKED"
2426
2427        return f"{lock_type}{expressions}{wait or ''}"
2428
2429    def literal_sql(self, expression: exp.Literal) -> str:
2430        text = expression.this or ""
2431        if expression.is_string:
2432            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2433        return text
2434
2435    def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2436        if self.dialect.ESCAPED_SEQUENCES:
2437            to_escaped = self.dialect.ESCAPED_SEQUENCES
2438            text = "".join(
2439                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2440            )
2441
2442        return self._replace_line_breaks(text).replace(
2443            self.dialect.QUOTE_END, self._escaped_quote_end
2444        )
2445
2446    def loaddata_sql(self, expression: exp.LoadData) -> str:
2447        local = " LOCAL" if expression.args.get("local") else ""
2448        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2449        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2450        this = f" INTO TABLE {self.sql(expression, 'this')}"
2451        partition = self.sql(expression, "partition")
2452        partition = f" {partition}" if partition else ""
2453        input_format = self.sql(expression, "input_format")
2454        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2455        serde = self.sql(expression, "serde")
2456        serde = f" SERDE {serde}" if serde else ""
2457        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2458
2459    def null_sql(self, *_) -> str:
2460        return "NULL"
2461
2462    def boolean_sql(self, expression: exp.Boolean) -> str:
2463        return "TRUE" if expression.this else "FALSE"
2464
2465    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2466        this = self.sql(expression, "this")
2467        this = f"{this} " if this else this
2468        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2469        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2470
2471    def withfill_sql(self, expression: exp.WithFill) -> str:
2472        from_sql = self.sql(expression, "from")
2473        from_sql = f" FROM {from_sql}" if from_sql else ""
2474        to_sql = self.sql(expression, "to")
2475        to_sql = f" TO {to_sql}" if to_sql else ""
2476        step_sql = self.sql(expression, "step")
2477        step_sql = f" STEP {step_sql}" if step_sql else ""
2478        interpolated_values = [
2479            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2480            if isinstance(e, exp.Alias)
2481            else self.sql(e, "this")
2482            for e in expression.args.get("interpolate") or []
2483        ]
2484        interpolate = (
2485            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2486        )
2487        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2488
2489    def cluster_sql(self, expression: exp.Cluster) -> str:
2490        return self.op_expressions("CLUSTER BY", expression)
2491
2492    def distribute_sql(self, expression: exp.Distribute) -> str:
2493        return self.op_expressions("DISTRIBUTE BY", expression)
2494
2495    def sort_sql(self, expression: exp.Sort) -> str:
2496        return self.op_expressions("SORT BY", expression)
2497
2498    def ordered_sql(self, expression: exp.Ordered) -> str:
2499        desc = expression.args.get("desc")
2500        asc = not desc
2501
2502        nulls_first = expression.args.get("nulls_first")
2503        nulls_last = not nulls_first
2504        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2505        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2506        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2507
2508        this = self.sql(expression, "this")
2509
2510        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2511        nulls_sort_change = ""
2512        if nulls_first and (
2513            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2514        ):
2515            nulls_sort_change = " NULLS FIRST"
2516        elif (
2517            nulls_last
2518            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2519            and not nulls_are_last
2520        ):
2521            nulls_sort_change = " NULLS LAST"
2522
2523        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2524        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2525            window = expression.find_ancestor(exp.Window, exp.Select)
2526            if isinstance(window, exp.Window) and window.args.get("spec"):
2527                self.unsupported(
2528                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2529                )
2530                nulls_sort_change = ""
2531            elif self.NULL_ORDERING_SUPPORTED is False and (
2532                (asc and nulls_sort_change == " NULLS LAST")
2533                or (desc and nulls_sort_change == " NULLS FIRST")
2534            ):
2535                # BigQuery does not allow these ordering/nulls combinations when used under
2536                # an aggregation func or under a window containing one
2537                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2538
2539                if isinstance(ancestor, exp.Window):
2540                    ancestor = ancestor.this
2541                if isinstance(ancestor, exp.AggFunc):
2542                    self.unsupported(
2543                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2544                    )
2545                    nulls_sort_change = ""
2546            elif self.NULL_ORDERING_SUPPORTED is None:
2547                if expression.this.is_int:
2548                    self.unsupported(
2549                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2550                    )
2551                elif not isinstance(expression.this, exp.Rand):
2552                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2553                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2554                nulls_sort_change = ""
2555
2556        with_fill = self.sql(expression, "with_fill")
2557        with_fill = f" {with_fill}" if with_fill else ""
2558
2559        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2560
2561    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2562        window_frame = self.sql(expression, "window_frame")
2563        window_frame = f"{window_frame} " if window_frame else ""
2564
2565        this = self.sql(expression, "this")
2566
2567        return f"{window_frame}{this}"
2568
2569    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2570        partition = self.partition_by_sql(expression)
2571        order = self.sql(expression, "order")
2572        measures = self.expressions(expression, key="measures")
2573        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2574        rows = self.sql(expression, "rows")
2575        rows = self.seg(rows) if rows else ""
2576        after = self.sql(expression, "after")
2577        after = self.seg(after) if after else ""
2578        pattern = self.sql(expression, "pattern")
2579        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2580        definition_sqls = [
2581            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2582            for definition in expression.args.get("define", [])
2583        ]
2584        definitions = self.expressions(sqls=definition_sqls)
2585        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2586        body = "".join(
2587            (
2588                partition,
2589                order,
2590                measures,
2591                rows,
2592                after,
2593                pattern,
2594                define,
2595            )
2596        )
2597        alias = self.sql(expression, "alias")
2598        alias = f" {alias}" if alias else ""
2599        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2600
2601    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2602        limit = expression.args.get("limit")
2603
2604        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2605            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2606        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2607            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2608
2609        return csv(
2610            *sqls,
2611            *[self.sql(join) for join in expression.args.get("joins") or []],
2612            self.sql(expression, "match"),
2613            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2614            self.sql(expression, "prewhere"),
2615            self.sql(expression, "where"),
2616            self.sql(expression, "connect"),
2617            self.sql(expression, "group"),
2618            self.sql(expression, "having"),
2619            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2620            self.sql(expression, "order"),
2621            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2622            *self.after_limit_modifiers(expression),
2623            self.options_modifier(expression),
2624            self.for_modifiers(expression),
2625            sep="",
2626        )
2627
2628    def options_modifier(self, expression: exp.Expression) -> str:
2629        options = self.expressions(expression, key="options")
2630        return f" {options}" if options else ""
2631
2632    def for_modifiers(self, expression: exp.Expression) -> str:
2633        for_modifiers = self.expressions(expression, key="for")
2634        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2635
2636    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2637        self.unsupported("Unsupported query option.")
2638        return ""
2639
2640    def offset_limit_modifiers(
2641        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2642    ) -> t.List[str]:
2643        return [
2644            self.sql(expression, "offset") if fetch else self.sql(limit),
2645            self.sql(limit) if fetch else self.sql(expression, "offset"),
2646        ]
2647
2648    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2649        locks = self.expressions(expression, key="locks", sep=" ")
2650        locks = f" {locks}" if locks else ""
2651        return [locks, self.sql(expression, "sample")]
2652
2653    def select_sql(self, expression: exp.Select) -> str:
2654        into = expression.args.get("into")
2655        if not self.SUPPORTS_SELECT_INTO and into:
2656            into.pop()
2657
2658        hint = self.sql(expression, "hint")
2659        distinct = self.sql(expression, "distinct")
2660        distinct = f" {distinct}" if distinct else ""
2661        kind = self.sql(expression, "kind")
2662
2663        limit = expression.args.get("limit")
2664        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2665            top = self.limit_sql(limit, top=True)
2666            limit.pop()
2667        else:
2668            top = ""
2669
2670        expressions = self.expressions(expression)
2671
2672        if kind:
2673            if kind in self.SELECT_KINDS:
2674                kind = f" AS {kind}"
2675            else:
2676                if kind == "STRUCT":
2677                    expressions = self.expressions(
2678                        sqls=[
2679                            self.sql(
2680                                exp.Struct(
2681                                    expressions=[
2682                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2683                                        if isinstance(e, exp.Alias)
2684                                        else e
2685                                        for e in expression.expressions
2686                                    ]
2687                                )
2688                            )
2689                        ]
2690                    )
2691                kind = ""
2692
2693        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2694        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2695
2696        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2697        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2698        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2699        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2700        sql = self.query_modifiers(
2701            expression,
2702            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2703            self.sql(expression, "into", comment=False),
2704            self.sql(expression, "from", comment=False),
2705        )
2706
2707        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2708        if expression.args.get("with"):
2709            sql = self.maybe_comment(sql, expression)
2710            expression.pop_comments()
2711
2712        sql = self.prepend_ctes(expression, sql)
2713
2714        if not self.SUPPORTS_SELECT_INTO and into:
2715            if into.args.get("temporary"):
2716                table_kind = " TEMPORARY"
2717            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2718                table_kind = " UNLOGGED"
2719            else:
2720                table_kind = ""
2721            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2722
2723        return sql
2724
2725    def schema_sql(self, expression: exp.Schema) -> str:
2726        this = self.sql(expression, "this")
2727        sql = self.schema_columns_sql(expression)
2728        return f"{this} {sql}" if this and sql else this or sql
2729
2730    def schema_columns_sql(self, expression: exp.Schema) -> str:
2731        if expression.expressions:
2732            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2733        return ""
2734
2735    def star_sql(self, expression: exp.Star) -> str:
2736        except_ = self.expressions(expression, key="except", flat=True)
2737        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2738        replace = self.expressions(expression, key="replace", flat=True)
2739        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2740        rename = self.expressions(expression, key="rename", flat=True)
2741        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2742        return f"*{except_}{replace}{rename}"
2743
2744    def parameter_sql(self, expression: exp.Parameter) -> str:
2745        this = self.sql(expression, "this")
2746        return f"{self.PARAMETER_TOKEN}{this}"
2747
2748    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2749        this = self.sql(expression, "this")
2750        kind = expression.text("kind")
2751        if kind:
2752            kind = f"{kind}."
2753        return f"@@{kind}{this}"
2754
2755    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2756        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2757
2758    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2759        alias = self.sql(expression, "alias")
2760        alias = f"{sep}{alias}" if alias else ""
2761        sample = self.sql(expression, "sample")
2762        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2763            alias = f"{sample}{alias}"
2764
2765            # Set to None so it's not generated again by self.query_modifiers()
2766            expression.set("sample", None)
2767
2768        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2769        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2770        return self.prepend_ctes(expression, sql)
2771
2772    def qualify_sql(self, expression: exp.Qualify) -> str:
2773        this = self.indent(self.sql(expression, "this"))
2774        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2775
2776    def unnest_sql(self, expression: exp.Unnest) -> str:
2777        args = self.expressions(expression, flat=True)
2778
2779        alias = expression.args.get("alias")
2780        offset = expression.args.get("offset")
2781
2782        if self.UNNEST_WITH_ORDINALITY:
2783            if alias and isinstance(offset, exp.Expression):
2784                alias.append("columns", offset)
2785
2786        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2787            columns = alias.columns
2788            alias = self.sql(columns[0]) if columns else ""
2789        else:
2790            alias = self.sql(alias)
2791
2792        alias = f" AS {alias}" if alias else alias
2793        if self.UNNEST_WITH_ORDINALITY:
2794            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2795        else:
2796            if isinstance(offset, exp.Expression):
2797                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2798            elif offset:
2799                suffix = f"{alias} WITH OFFSET"
2800            else:
2801                suffix = alias
2802
2803        return f"UNNEST({args}){suffix}"
2804
2805    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2806        return ""
2807
2808    def where_sql(self, expression: exp.Where) -> str:
2809        this = self.indent(self.sql(expression, "this"))
2810        return f"{self.seg('WHERE')}{self.sep()}{this}"
2811
2812    def window_sql(self, expression: exp.Window) -> str:
2813        this = self.sql(expression, "this")
2814        partition = self.partition_by_sql(expression)
2815        order = expression.args.get("order")
2816        order = self.order_sql(order, flat=True) if order else ""
2817        spec = self.sql(expression, "spec")
2818        alias = self.sql(expression, "alias")
2819        over = self.sql(expression, "over") or "OVER"
2820
2821        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2822
2823        first = expression.args.get("first")
2824        if first is None:
2825            first = ""
2826        else:
2827            first = "FIRST" if first else "LAST"
2828
2829        if not partition and not order and not spec and alias:
2830            return f"{this} {alias}"
2831
2832        args = self.format_args(
2833            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2834        )
2835        return f"{this} ({args})"
2836
2837    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2838        partition = self.expressions(expression, key="partition_by", flat=True)
2839        return f"PARTITION BY {partition}" if partition else ""
2840
2841    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2842        kind = self.sql(expression, "kind")
2843        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2844        end = (
2845            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2846            or "CURRENT ROW"
2847        )
2848
2849        window_spec = f"{kind} BETWEEN {start} AND {end}"
2850
2851        exclude = self.sql(expression, "exclude")
2852        if exclude:
2853            if self.SUPPORTS_WINDOW_EXCLUDE:
2854                window_spec += f" EXCLUDE {exclude}"
2855            else:
2856                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2857
2858        return window_spec
2859
2860    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2861        this = self.sql(expression, "this")
2862        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2863        return f"{this} WITHIN GROUP ({expression_sql})"
2864
2865    def between_sql(self, expression: exp.Between) -> str:
2866        this = self.sql(expression, "this")
2867        low = self.sql(expression, "low")
2868        high = self.sql(expression, "high")
2869        return f"{this} BETWEEN {low} AND {high}"
2870
2871    def bracket_offset_expressions(
2872        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2873    ) -> t.List[exp.Expression]:
2874        return apply_index_offset(
2875            expression.this,
2876            expression.expressions,
2877            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2878            dialect=self.dialect,
2879        )
2880
2881    def bracket_sql(self, expression: exp.Bracket) -> str:
2882        expressions = self.bracket_offset_expressions(expression)
2883        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2884        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2885
2886    def all_sql(self, expression: exp.All) -> str:
2887        return f"ALL {self.wrap(expression)}"
2888
2889    def any_sql(self, expression: exp.Any) -> str:
2890        this = self.sql(expression, "this")
2891        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2892            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2893                this = self.wrap(this)
2894            return f"ANY{this}"
2895        return f"ANY {this}"
2896
2897    def exists_sql(self, expression: exp.Exists) -> str:
2898        return f"EXISTS{self.wrap(expression)}"
2899
2900    def case_sql(self, expression: exp.Case) -> str:
2901        this = self.sql(expression, "this")
2902        statements = [f"CASE {this}" if this else "CASE"]
2903
2904        for e in expression.args["ifs"]:
2905            statements.append(f"WHEN {self.sql(e, 'this')}")
2906            statements.append(f"THEN {self.sql(e, 'true')}")
2907
2908        default = self.sql(expression, "default")
2909
2910        if default:
2911            statements.append(f"ELSE {default}")
2912
2913        statements.append("END")
2914
2915        if self.pretty and self.too_wide(statements):
2916            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2917
2918        return " ".join(statements)
2919
2920    def constraint_sql(self, expression: exp.Constraint) -> str:
2921        this = self.sql(expression, "this")
2922        expressions = self.expressions(expression, flat=True)
2923        return f"CONSTRAINT {this} {expressions}"
2924
2925    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2926        order = expression.args.get("order")
2927        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2928        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
2929
2930    def extract_sql(self, expression: exp.Extract) -> str:
2931        from sqlglot.dialects.dialect import map_date_part
2932
2933        this = (
2934            map_date_part(expression.this, self.dialect)
2935            if self.NORMALIZE_EXTRACT_DATE_PARTS
2936            else expression.this
2937        )
2938        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
2939        expression_sql = self.sql(expression, "expression")
2940
2941        return f"EXTRACT({this_sql} FROM {expression_sql})"
2942
2943    def trim_sql(self, expression: exp.Trim) -> str:
2944        trim_type = self.sql(expression, "position")
2945
2946        if trim_type == "LEADING":
2947            func_name = "LTRIM"
2948        elif trim_type == "TRAILING":
2949            func_name = "RTRIM"
2950        else:
2951            func_name = "TRIM"
2952
2953        return self.func(func_name, expression.this, expression.expression)
2954
2955    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
2956        args = expression.expressions
2957        if isinstance(expression, exp.ConcatWs):
2958            args = args[1:]  # Skip the delimiter
2959
2960        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
2961            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2962
2963        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2964            args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
2965
2966        return args
2967
2968    def concat_sql(self, expression: exp.Concat) -> str:
2969        expressions = self.convert_concat_args(expression)
2970
2971        # Some dialects don't allow a single-argument CONCAT call
2972        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
2973            return self.sql(expressions[0])
2974
2975        return self.func("CONCAT", *expressions)
2976
2977    def concatws_sql(self, expression: exp.ConcatWs) -> str:
2978        return self.func(
2979            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
2980        )
2981
2982    def check_sql(self, expression: exp.Check) -> str:
2983        this = self.sql(expression, key="this")
2984        return f"CHECK ({this})"
2985
2986    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
2987        expressions = self.expressions(expression, flat=True)
2988        expressions = f" ({expressions})" if expressions else ""
2989        reference = self.sql(expression, "reference")
2990        reference = f" {reference}" if reference else ""
2991        delete = self.sql(expression, "delete")
2992        delete = f" ON DELETE {delete}" if delete else ""
2993        update = self.sql(expression, "update")
2994        update = f" ON UPDATE {update}" if update else ""
2995        options = self.expressions(expression, key="options", flat=True, sep=" ")
2996        options = f" {options}" if options else ""
2997        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
2998
2999    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
3000        expressions = self.expressions(expression, flat=True)
3001        options = self.expressions(expression, key="options", flat=True, sep=" ")
3002        options = f" {options}" if options else ""
3003        return f"PRIMARY KEY ({expressions}){options}"
3004
3005    def if_sql(self, expression: exp.If) -> str:
3006        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3007
3008    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3009        modifier = expression.args.get("modifier")
3010        modifier = f" {modifier}" if modifier else ""
3011        return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3012
3013    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3014        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3015
3016    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3017        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3018
3019        if expression.args.get("escape"):
3020            path = self.escape_str(path)
3021
3022        if self.QUOTE_JSON_PATH:
3023            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3024
3025        return path
3026
3027    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3028        if isinstance(expression, exp.JSONPathPart):
3029            transform = self.TRANSFORMS.get(expression.__class__)
3030            if not callable(transform):
3031                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3032                return ""
3033
3034            return transform(self, expression)
3035
3036        if isinstance(expression, int):
3037            return str(expression)
3038
3039        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3040            escaped = expression.replace("'", "\\'")
3041            escaped = f"\\'{expression}\\'"
3042        else:
3043            escaped = expression.replace('"', '\\"')
3044            escaped = f'"{escaped}"'
3045
3046        return escaped
3047
3048    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3049        return f"{self.sql(expression, 'this')} FORMAT JSON"
3050
3051    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3052        # Output the Teradata column FORMAT override.
3053        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3054        this = self.sql(expression, "this")
3055        fmt = self.sql(expression, "format")
3056        return f"{this} (FORMAT {fmt})"
3057
3058    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3059        null_handling = expression.args.get("null_handling")
3060        null_handling = f" {null_handling}" if null_handling else ""
3061
3062        unique_keys = expression.args.get("unique_keys")
3063        if unique_keys is not None:
3064            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3065        else:
3066            unique_keys = ""
3067
3068        return_type = self.sql(expression, "return_type")
3069        return_type = f" RETURNING {return_type}" if return_type else ""
3070        encoding = self.sql(expression, "encoding")
3071        encoding = f" ENCODING {encoding}" if encoding else ""
3072
3073        return self.func(
3074            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3075            *expression.expressions,
3076            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3077        )
3078
3079    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3080        return self.jsonobject_sql(expression)
3081
3082    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3083        null_handling = expression.args.get("null_handling")
3084        null_handling = f" {null_handling}" if null_handling else ""
3085        return_type = self.sql(expression, "return_type")
3086        return_type = f" RETURNING {return_type}" if return_type else ""
3087        strict = " STRICT" if expression.args.get("strict") else ""
3088        return self.func(
3089            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3090        )
3091
3092    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3093        this = self.sql(expression, "this")
3094        order = self.sql(expression, "order")
3095        null_handling = expression.args.get("null_handling")
3096        null_handling = f" {null_handling}" if null_handling else ""
3097        return_type = self.sql(expression, "return_type")
3098        return_type = f" RETURNING {return_type}" if return_type else ""
3099        strict = " STRICT" if expression.args.get("strict") else ""
3100        return self.func(
3101            "JSON_ARRAYAGG",
3102            this,
3103            suffix=f"{order}{null_handling}{return_type}{strict})",
3104        )
3105
3106    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3107        path = self.sql(expression, "path")
3108        path = f" PATH {path}" if path else ""
3109        nested_schema = self.sql(expression, "nested_schema")
3110
3111        if nested_schema:
3112            return f"NESTED{path} {nested_schema}"
3113
3114        this = self.sql(expression, "this")
3115        kind = self.sql(expression, "kind")
3116        kind = f" {kind}" if kind else ""
3117        return f"{this}{kind}{path}"
3118
3119    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3120        return self.func("COLUMNS", *expression.expressions)
3121
3122    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3123        this = self.sql(expression, "this")
3124        path = self.sql(expression, "path")
3125        path = f", {path}" if path else ""
3126        error_handling = expression.args.get("error_handling")
3127        error_handling = f" {error_handling}" if error_handling else ""
3128        empty_handling = expression.args.get("empty_handling")
3129        empty_handling = f" {empty_handling}" if empty_handling else ""
3130        schema = self.sql(expression, "schema")
3131        return self.func(
3132            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3133        )
3134
3135    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3136        this = self.sql(expression, "this")
3137        kind = self.sql(expression, "kind")
3138        path = self.sql(expression, "path")
3139        path = f" {path}" if path else ""
3140        as_json = " AS JSON" if expression.args.get("as_json") else ""
3141        return f"{this} {kind}{path}{as_json}"
3142
3143    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3144        this = self.sql(expression, "this")
3145        path = self.sql(expression, "path")
3146        path = f", {path}" if path else ""
3147        expressions = self.expressions(expression)
3148        with_ = (
3149            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3150            if expressions
3151            else ""
3152        )
3153        return f"OPENJSON({this}{path}){with_}"
3154
3155    def in_sql(self, expression: exp.In) -> str:
3156        query = expression.args.get("query")
3157        unnest = expression.args.get("unnest")
3158        field = expression.args.get("field")
3159        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3160
3161        if query:
3162            in_sql = self.sql(query)
3163        elif unnest:
3164            in_sql = self.in_unnest_op(unnest)
3165        elif field:
3166            in_sql = self.sql(field)
3167        else:
3168            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3169
3170        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3171
3172    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3173        return f"(SELECT {self.sql(unnest)})"
3174
3175    def interval_sql(self, expression: exp.Interval) -> str:
3176        unit = self.sql(expression, "unit")
3177        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3178            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3179        unit = f" {unit}" if unit else ""
3180
3181        if self.SINGLE_STRING_INTERVAL:
3182            this = expression.this.name if expression.this else ""
3183            return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3184
3185        this = self.sql(expression, "this")
3186        if this:
3187            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3188            this = f" {this}" if unwrapped else f" ({this})"
3189
3190        return f"INTERVAL{this}{unit}"
3191
3192    def return_sql(self, expression: exp.Return) -> str:
3193        return f"RETURN {self.sql(expression, 'this')}"
3194
3195    def reference_sql(self, expression: exp.Reference) -> str:
3196        this = self.sql(expression, "this")
3197        expressions = self.expressions(expression, flat=True)
3198        expressions = f"({expressions})" if expressions else ""
3199        options = self.expressions(expression, key="options", flat=True, sep=" ")
3200        options = f" {options}" if options else ""
3201        return f"REFERENCES {this}{expressions}{options}"
3202
3203    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3204        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3205        parent = expression.parent
3206        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3207        return self.func(
3208            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3209        )
3210
3211    def paren_sql(self, expression: exp.Paren) -> str:
3212        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3213        return f"({sql}{self.seg(')', sep='')}"
3214
3215    def neg_sql(self, expression: exp.Neg) -> str:
3216        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3217        this_sql = self.sql(expression, "this")
3218        sep = " " if this_sql[0] == "-" else ""
3219        return f"-{sep}{this_sql}"
3220
3221    def not_sql(self, expression: exp.Not) -> str:
3222        return f"NOT {self.sql(expression, 'this')}"
3223
3224    def alias_sql(self, expression: exp.Alias) -> str:
3225        alias = self.sql(expression, "alias")
3226        alias = f" AS {alias}" if alias else ""
3227        return f"{self.sql(expression, 'this')}{alias}"
3228
3229    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3230        alias = expression.args["alias"]
3231
3232        parent = expression.parent
3233        pivot = parent and parent.parent
3234
3235        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3236            identifier_alias = isinstance(alias, exp.Identifier)
3237            literal_alias = isinstance(alias, exp.Literal)
3238
3239            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3240                alias.replace(exp.Literal.string(alias.output_name))
3241            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3242                alias.replace(exp.to_identifier(alias.output_name))
3243
3244        return self.alias_sql(expression)
3245
3246    def aliases_sql(self, expression: exp.Aliases) -> str:
3247        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3248
3249    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3250        this = self.sql(expression, "this")
3251        index = self.sql(expression, "expression")
3252        return f"{this} AT {index}"
3253
3254    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3255        this = self.sql(expression, "this")
3256        zone = self.sql(expression, "zone")
3257        return f"{this} AT TIME ZONE {zone}"
3258
3259    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3260        this = self.sql(expression, "this")
3261        zone = self.sql(expression, "zone")
3262        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3263
3264    def add_sql(self, expression: exp.Add) -> str:
3265        return self.binary(expression, "+")
3266
3267    def and_sql(
3268        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3269    ) -> str:
3270        return self.connector_sql(expression, "AND", stack)
3271
3272    def or_sql(
3273        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3274    ) -> str:
3275        return self.connector_sql(expression, "OR", stack)
3276
3277    def xor_sql(
3278        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3279    ) -> str:
3280        return self.connector_sql(expression, "XOR", stack)
3281
3282    def connector_sql(
3283        self,
3284        expression: exp.Connector,
3285        op: str,
3286        stack: t.Optional[t.List[str | exp.Expression]] = None,
3287    ) -> str:
3288        if stack is not None:
3289            if expression.expressions:
3290                stack.append(self.expressions(expression, sep=f" {op} "))
3291            else:
3292                stack.append(expression.right)
3293                if expression.comments and self.comments:
3294                    for comment in expression.comments:
3295                        if comment:
3296                            op += f" /*{self.sanitize_comment(comment)}*/"
3297                stack.extend((op, expression.left))
3298            return op
3299
3300        stack = [expression]
3301        sqls: t.List[str] = []
3302        ops = set()
3303
3304        while stack:
3305            node = stack.pop()
3306            if isinstance(node, exp.Connector):
3307                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3308            else:
3309                sql = self.sql(node)
3310                if sqls and sqls[-1] in ops:
3311                    sqls[-1] += f" {sql}"
3312                else:
3313                    sqls.append(sql)
3314
3315        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3316        return sep.join(sqls)
3317
3318    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3319        return self.binary(expression, "&")
3320
3321    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3322        return self.binary(expression, "<<")
3323
3324    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3325        return f"~{self.sql(expression, 'this')}"
3326
3327    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3328        return self.binary(expression, "|")
3329
3330    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3331        return self.binary(expression, ">>")
3332
3333    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3334        return self.binary(expression, "^")
3335
3336    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3337        format_sql = self.sql(expression, "format")
3338        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3339        to_sql = self.sql(expression, "to")
3340        to_sql = f" {to_sql}" if to_sql else ""
3341        action = self.sql(expression, "action")
3342        action = f" {action}" if action else ""
3343        default = self.sql(expression, "default")
3344        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3345        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3346
3347    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3348        zone = self.sql(expression, "this")
3349        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3350
3351    def collate_sql(self, expression: exp.Collate) -> str:
3352        if self.COLLATE_IS_FUNC:
3353            return self.function_fallback_sql(expression)
3354        return self.binary(expression, "COLLATE")
3355
3356    def command_sql(self, expression: exp.Command) -> str:
3357        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3358
3359    def comment_sql(self, expression: exp.Comment) -> str:
3360        this = self.sql(expression, "this")
3361        kind = expression.args["kind"]
3362        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3363        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3364        expression_sql = self.sql(expression, "expression")
3365        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3366
3367    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3368        this = self.sql(expression, "this")
3369        delete = " DELETE" if expression.args.get("delete") else ""
3370        recompress = self.sql(expression, "recompress")
3371        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3372        to_disk = self.sql(expression, "to_disk")
3373        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3374        to_volume = self.sql(expression, "to_volume")
3375        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3376        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3377
3378    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3379        where = self.sql(expression, "where")
3380        group = self.sql(expression, "group")
3381        aggregates = self.expressions(expression, key="aggregates")
3382        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3383
3384        if not (where or group or aggregates) and len(expression.expressions) == 1:
3385            return f"TTL {self.expressions(expression, flat=True)}"
3386
3387        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3388
3389    def transaction_sql(self, expression: exp.Transaction) -> str:
3390        return "BEGIN"
3391
3392    def commit_sql(self, expression: exp.Commit) -> str:
3393        chain = expression.args.get("chain")
3394        if chain is not None:
3395            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3396
3397        return f"COMMIT{chain or ''}"
3398
3399    def rollback_sql(self, expression: exp.Rollback) -> str:
3400        savepoint = expression.args.get("savepoint")
3401        savepoint = f" TO {savepoint}" if savepoint else ""
3402        return f"ROLLBACK{savepoint}"
3403
3404    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3405        this = self.sql(expression, "this")
3406
3407        dtype = self.sql(expression, "dtype")
3408        if dtype:
3409            collate = self.sql(expression, "collate")
3410            collate = f" COLLATE {collate}" if collate else ""
3411            using = self.sql(expression, "using")
3412            using = f" USING {using}" if using else ""
3413            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3414            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3415
3416        default = self.sql(expression, "default")
3417        if default:
3418            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3419
3420        comment = self.sql(expression, "comment")
3421        if comment:
3422            return f"ALTER COLUMN {this} COMMENT {comment}"
3423
3424        visible = expression.args.get("visible")
3425        if visible:
3426            return f"ALTER COLUMN {this} SET {visible}"
3427
3428        allow_null = expression.args.get("allow_null")
3429        drop = expression.args.get("drop")
3430
3431        if not drop and not allow_null:
3432            self.unsupported("Unsupported ALTER COLUMN syntax")
3433
3434        if allow_null is not None:
3435            keyword = "DROP" if drop else "SET"
3436            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3437
3438        return f"ALTER COLUMN {this} DROP DEFAULT"
3439
3440    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3441        this = self.sql(expression, "this")
3442
3443        visible = expression.args.get("visible")
3444        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3445
3446        return f"ALTER INDEX {this} {visible_sql}"
3447
3448    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3449        this = self.sql(expression, "this")
3450        if not isinstance(expression.this, exp.Var):
3451            this = f"KEY DISTKEY {this}"
3452        return f"ALTER DISTSTYLE {this}"
3453
3454    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3455        compound = " COMPOUND" if expression.args.get("compound") else ""
3456        this = self.sql(expression, "this")
3457        expressions = self.expressions(expression, flat=True)
3458        expressions = f"({expressions})" if expressions else ""
3459        return f"ALTER{compound} SORTKEY {this or expressions}"
3460
3461    def alterrename_sql(self, expression: exp.AlterRename) -> str:
3462        if not self.RENAME_TABLE_WITH_DB:
3463            # Remove db from tables
3464            expression = expression.transform(
3465                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3466            ).assert_is(exp.AlterRename)
3467        this = self.sql(expression, "this")
3468        return f"RENAME TO {this}"
3469
3470    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3471        exists = " IF EXISTS" if expression.args.get("exists") else ""
3472        old_column = self.sql(expression, "this")
3473        new_column = self.sql(expression, "to")
3474        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3475
3476    def alterset_sql(self, expression: exp.AlterSet) -> str:
3477        exprs = self.expressions(expression, flat=True)
3478        if self.ALTER_SET_WRAPPED:
3479            exprs = f"({exprs})"
3480
3481        return f"SET {exprs}"
3482
3483    def alter_sql(self, expression: exp.Alter) -> str:
3484        actions = expression.args["actions"]
3485
3486        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3487            actions[0], exp.ColumnDef
3488        ):
3489            actions_sql = self.expressions(expression, key="actions", flat=True)
3490            actions_sql = f"ADD {actions_sql}"
3491        else:
3492            actions_list = []
3493            for action in actions:
3494                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3495                    action_sql = self.add_column_sql(action)
3496                else:
3497                    action_sql = self.sql(action)
3498                    if isinstance(action, exp.Query):
3499                        action_sql = f"AS {action_sql}"
3500
3501                actions_list.append(action_sql)
3502
3503            actions_sql = self.format_args(*actions_list).lstrip("\n")
3504
3505        exists = " IF EXISTS" if expression.args.get("exists") else ""
3506        on_cluster = self.sql(expression, "cluster")
3507        on_cluster = f" {on_cluster}" if on_cluster else ""
3508        only = " ONLY" if expression.args.get("only") else ""
3509        options = self.expressions(expression, key="options")
3510        options = f", {options}" if options else ""
3511        kind = self.sql(expression, "kind")
3512        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3513
3514        return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3515
3516    def add_column_sql(self, expression: exp.Expression) -> str:
3517        sql = self.sql(expression)
3518        if isinstance(expression, exp.Schema):
3519            column_text = " COLUMNS"
3520        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3521            column_text = " COLUMN"
3522        else:
3523            column_text = ""
3524
3525        return f"ADD{column_text} {sql}"
3526
3527    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3528        expressions = self.expressions(expression)
3529        exists = " IF EXISTS " if expression.args.get("exists") else " "
3530        return f"DROP{exists}{expressions}"
3531
3532    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3533        return f"ADD {self.expressions(expression, indent=False)}"
3534
3535    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3536        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3537        return f"ADD {exists}{self.sql(expression.this)}"
3538
3539    def distinct_sql(self, expression: exp.Distinct) -> str:
3540        this = self.expressions(expression, flat=True)
3541
3542        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3543            case = exp.case()
3544            for arg in expression.expressions:
3545                case = case.when(arg.is_(exp.null()), exp.null())
3546            this = self.sql(case.else_(f"({this})"))
3547
3548        this = f" {this}" if this else ""
3549
3550        on = self.sql(expression, "on")
3551        on = f" ON {on}" if on else ""
3552        return f"DISTINCT{this}{on}"
3553
3554    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3555        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3556
3557    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3558        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3559
3560    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3561        this_sql = self.sql(expression, "this")
3562        expression_sql = self.sql(expression, "expression")
3563        kind = "MAX" if expression.args.get("max") else "MIN"
3564        return f"{this_sql} HAVING {kind} {expression_sql}"
3565
3566    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3567        return self.sql(
3568            exp.Cast(
3569                this=exp.Div(this=expression.this, expression=expression.expression),
3570                to=exp.DataType(this=exp.DataType.Type.INT),
3571            )
3572        )
3573
3574    def dpipe_sql(self, expression: exp.DPipe) -> str:
3575        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3576            return self.func(
3577                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3578            )
3579        return self.binary(expression, "||")
3580
3581    def div_sql(self, expression: exp.Div) -> str:
3582        l, r = expression.left, expression.right
3583
3584        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3585            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3586
3587        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3588            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3589                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3590
3591        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3592            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3593                return self.sql(
3594                    exp.cast(
3595                        l / r,
3596                        to=exp.DataType.Type.BIGINT,
3597                    )
3598                )
3599
3600        return self.binary(expression, "/")
3601
3602    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3603        n = exp._wrap(expression.this, exp.Binary)
3604        d = exp._wrap(expression.expression, exp.Binary)
3605        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3606
3607    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3608        return self.binary(expression, "OVERLAPS")
3609
3610    def distance_sql(self, expression: exp.Distance) -> str:
3611        return self.binary(expression, "<->")
3612
3613    def dot_sql(self, expression: exp.Dot) -> str:
3614        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3615
3616    def eq_sql(self, expression: exp.EQ) -> str:
3617        return self.binary(expression, "=")
3618
3619    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3620        return self.binary(expression, ":=")
3621
3622    def escape_sql(self, expression: exp.Escape) -> str:
3623        return self.binary(expression, "ESCAPE")
3624
3625    def glob_sql(self, expression: exp.Glob) -> str:
3626        return self.binary(expression, "GLOB")
3627
3628    def gt_sql(self, expression: exp.GT) -> str:
3629        return self.binary(expression, ">")
3630
3631    def gte_sql(self, expression: exp.GTE) -> str:
3632        return self.binary(expression, ">=")
3633
3634    def ilike_sql(self, expression: exp.ILike) -> str:
3635        return self.binary(expression, "ILIKE")
3636
3637    def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
3638        return self.binary(expression, "ILIKE ANY")
3639
3640    def is_sql(self, expression: exp.Is) -> str:
3641        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3642            return self.sql(
3643                expression.this if expression.expression.this else exp.not_(expression.this)
3644            )
3645        return self.binary(expression, "IS")
3646
3647    def like_sql(self, expression: exp.Like) -> str:
3648        return self.binary(expression, "LIKE")
3649
3650    def likeany_sql(self, expression: exp.LikeAny) -> str:
3651        return self.binary(expression, "LIKE ANY")
3652
3653    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3654        return self.binary(expression, "SIMILAR TO")
3655
3656    def lt_sql(self, expression: exp.LT) -> str:
3657        return self.binary(expression, "<")
3658
3659    def lte_sql(self, expression: exp.LTE) -> str:
3660        return self.binary(expression, "<=")
3661
3662    def mod_sql(self, expression: exp.Mod) -> str:
3663        return self.binary(expression, "%")
3664
3665    def mul_sql(self, expression: exp.Mul) -> str:
3666        return self.binary(expression, "*")
3667
3668    def neq_sql(self, expression: exp.NEQ) -> str:
3669        return self.binary(expression, "<>")
3670
3671    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3672        return self.binary(expression, "IS NOT DISTINCT FROM")
3673
3674    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3675        return self.binary(expression, "IS DISTINCT FROM")
3676
3677    def slice_sql(self, expression: exp.Slice) -> str:
3678        return self.binary(expression, ":")
3679
3680    def sub_sql(self, expression: exp.Sub) -> str:
3681        return self.binary(expression, "-")
3682
3683    def trycast_sql(self, expression: exp.TryCast) -> str:
3684        return self.cast_sql(expression, safe_prefix="TRY_")
3685
3686    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3687        return self.cast_sql(expression)
3688
3689    def try_sql(self, expression: exp.Try) -> str:
3690        if not self.TRY_SUPPORTED:
3691            self.unsupported("Unsupported TRY function")
3692            return self.sql(expression, "this")
3693
3694        return self.func("TRY", expression.this)
3695
3696    def log_sql(self, expression: exp.Log) -> str:
3697        this = expression.this
3698        expr = expression.expression
3699
3700        if self.dialect.LOG_BASE_FIRST is False:
3701            this, expr = expr, this
3702        elif self.dialect.LOG_BASE_FIRST is None and expr:
3703            if this.name in ("2", "10"):
3704                return self.func(f"LOG{this.name}", expr)
3705
3706            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3707
3708        return self.func("LOG", this, expr)
3709
3710    def use_sql(self, expression: exp.Use) -> str:
3711        kind = self.sql(expression, "kind")
3712        kind = f" {kind}" if kind else ""
3713        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3714        this = f" {this}" if this else ""
3715        return f"USE{kind}{this}"
3716
3717    def binary(self, expression: exp.Binary, op: str) -> str:
3718        sqls: t.List[str] = []
3719        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3720        binary_type = type(expression)
3721
3722        while stack:
3723            node = stack.pop()
3724
3725            if type(node) is binary_type:
3726                op_func = node.args.get("operator")
3727                if op_func:
3728                    op = f"OPERATOR({self.sql(op_func)})"
3729
3730                stack.append(node.right)
3731                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3732                stack.append(node.left)
3733            else:
3734                sqls.append(self.sql(node))
3735
3736        return "".join(sqls)
3737
3738    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3739        to_clause = self.sql(expression, "to")
3740        if to_clause:
3741            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3742
3743        return self.function_fallback_sql(expression)
3744
3745    def function_fallback_sql(self, expression: exp.Func) -> str:
3746        args = []
3747
3748        for key in expression.arg_types:
3749            arg_value = expression.args.get(key)
3750
3751            if isinstance(arg_value, list):
3752                for value in arg_value:
3753                    args.append(value)
3754            elif arg_value is not None:
3755                args.append(arg_value)
3756
3757        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3758            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3759        else:
3760            name = expression.sql_name()
3761
3762        return self.func(name, *args)
3763
3764    def func(
3765        self,
3766        name: str,
3767        *args: t.Optional[exp.Expression | str],
3768        prefix: str = "(",
3769        suffix: str = ")",
3770        normalize: bool = True,
3771    ) -> str:
3772        name = self.normalize_func(name) if normalize else name
3773        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3774
3775    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3776        arg_sqls = tuple(
3777            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3778        )
3779        if self.pretty and self.too_wide(arg_sqls):
3780            return self.indent(
3781                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3782            )
3783        return sep.join(arg_sqls)
3784
3785    def too_wide(self, args: t.Iterable) -> bool:
3786        return sum(len(arg) for arg in args) > self.max_text_width
3787
3788    def format_time(
3789        self,
3790        expression: exp.Expression,
3791        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3792        inverse_time_trie: t.Optional[t.Dict] = None,
3793    ) -> t.Optional[str]:
3794        return format_time(
3795            self.sql(expression, "format"),
3796            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3797            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3798        )
3799
3800    def expressions(
3801        self,
3802        expression: t.Optional[exp.Expression] = None,
3803        key: t.Optional[str] = None,
3804        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3805        flat: bool = False,
3806        indent: bool = True,
3807        skip_first: bool = False,
3808        skip_last: bool = False,
3809        sep: str = ", ",
3810        prefix: str = "",
3811        dynamic: bool = False,
3812        new_line: bool = False,
3813    ) -> str:
3814        expressions = expression.args.get(key or "expressions") if expression else sqls
3815
3816        if not expressions:
3817            return ""
3818
3819        if flat:
3820            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3821
3822        num_sqls = len(expressions)
3823        result_sqls = []
3824
3825        for i, e in enumerate(expressions):
3826            sql = self.sql(e, comment=False)
3827            if not sql:
3828                continue
3829
3830            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3831
3832            if self.pretty:
3833                if self.leading_comma:
3834                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3835                else:
3836                    result_sqls.append(
3837                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3838                    )
3839            else:
3840                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3841
3842        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3843            if new_line:
3844                result_sqls.insert(0, "")
3845                result_sqls.append("")
3846            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3847        else:
3848            result_sql = "".join(result_sqls)
3849
3850        return (
3851            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
3852            if indent
3853            else result_sql
3854        )
3855
3856    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
3857        flat = flat or isinstance(expression.parent, exp.Properties)
3858        expressions_sql = self.expressions(expression, flat=flat)
3859        if flat:
3860            return f"{op} {expressions_sql}"
3861        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3862
3863    def naked_property(self, expression: exp.Property) -> str:
3864        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
3865        if not property_name:
3866            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
3867        return f"{property_name} {self.sql(expression, 'this')}"
3868
3869    def tag_sql(self, expression: exp.Tag) -> str:
3870        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
3871
3872    def token_sql(self, token_type: TokenType) -> str:
3873        return self.TOKEN_MAPPING.get(token_type, token_type.name)
3874
3875    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
3876        this = self.sql(expression, "this")
3877        expressions = self.no_identify(self.expressions, expression)
3878        expressions = (
3879            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
3880        )
3881        return f"{this}{expressions}" if expressions.strip() != "" else this
3882
3883    def joinhint_sql(self, expression: exp.JoinHint) -> str:
3884        this = self.sql(expression, "this")
3885        expressions = self.expressions(expression, flat=True)
3886        return f"{this}({expressions})"
3887
3888    def kwarg_sql(self, expression: exp.Kwarg) -> str:
3889        return self.binary(expression, "=>")
3890
3891    def when_sql(self, expression: exp.When) -> str:
3892        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
3893        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
3894        condition = self.sql(expression, "condition")
3895        condition = f" AND {condition}" if condition else ""
3896
3897        then_expression = expression.args.get("then")
3898        if isinstance(then_expression, exp.Insert):
3899            this = self.sql(then_expression, "this")
3900            this = f"INSERT {this}" if this else "INSERT"
3901            then = self.sql(then_expression, "expression")
3902            then = f"{this} VALUES {then}" if then else this
3903        elif isinstance(then_expression, exp.Update):
3904            if isinstance(then_expression.args.get("expressions"), exp.Star):
3905                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
3906            else:
3907                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
3908        else:
3909            then = self.sql(then_expression)
3910        return f"WHEN {matched}{source}{condition} THEN {then}"
3911
3912    def whens_sql(self, expression: exp.Whens) -> str:
3913        return self.expressions(expression, sep=" ", indent=False)
3914
3915    def merge_sql(self, expression: exp.Merge) -> str:
3916        table = expression.this
3917        table_alias = ""
3918
3919        hints = table.args.get("hints")
3920        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
3921            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
3922            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
3923
3924        this = self.sql(table)
3925        using = f"USING {self.sql(expression, 'using')}"
3926        on = f"ON {self.sql(expression, 'on')}"
3927        whens = self.sql(expression, "whens")
3928
3929        returning = self.sql(expression, "returning")
3930        if returning:
3931            whens = f"{whens}{returning}"
3932
3933        sep = self.sep()
3934
3935        return self.prepend_ctes(
3936            expression,
3937            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
3938        )
3939
3940    @unsupported_args("format")
3941    def tochar_sql(self, expression: exp.ToChar) -> str:
3942        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
3943
3944    def tonumber_sql(self, expression: exp.ToNumber) -> str:
3945        if not self.SUPPORTS_TO_NUMBER:
3946            self.unsupported("Unsupported TO_NUMBER function")
3947            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3948
3949        fmt = expression.args.get("format")
3950        if not fmt:
3951            self.unsupported("Conversion format is required for TO_NUMBER")
3952            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3953
3954        return self.func("TO_NUMBER", expression.this, fmt)
3955
3956    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
3957        this = self.sql(expression, "this")
3958        kind = self.sql(expression, "kind")
3959        settings_sql = self.expressions(expression, key="settings", sep=" ")
3960        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
3961        return f"{this}({kind}{args})"
3962
3963    def dictrange_sql(self, expression: exp.DictRange) -> str:
3964        this = self.sql(expression, "this")
3965        max = self.sql(expression, "max")
3966        min = self.sql(expression, "min")
3967        return f"{this}(MIN {min} MAX {max})"
3968
3969    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
3970        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
3971
3972    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
3973        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
3974
3975    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
3976    def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
3977        return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
3978
3979    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
3980    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
3981        expressions = self.expressions(expression, flat=True)
3982        expressions = f" {self.wrap(expressions)}" if expressions else ""
3983        buckets = self.sql(expression, "buckets")
3984        kind = self.sql(expression, "kind")
3985        buckets = f" BUCKETS {buckets}" if buckets else ""
3986        order = self.sql(expression, "order")
3987        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3988
3989    def oncluster_sql(self, expression: exp.OnCluster) -> str:
3990        return ""
3991
3992    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
3993        expressions = self.expressions(expression, key="expressions", flat=True)
3994        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
3995        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
3996        buckets = self.sql(expression, "buckets")
3997        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3998
3999    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4000        this = self.sql(expression, "this")
4001        having = self.sql(expression, "having")
4002
4003        if having:
4004            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4005
4006        return self.func("ANY_VALUE", this)
4007
4008    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4009        transform = self.func("TRANSFORM", *expression.expressions)
4010        row_format_before = self.sql(expression, "row_format_before")
4011        row_format_before = f" {row_format_before}" if row_format_before else ""
4012        record_writer = self.sql(expression, "record_writer")
4013        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4014        using = f" USING {self.sql(expression, 'command_script')}"
4015        schema = self.sql(expression, "schema")
4016        schema = f" AS {schema}" if schema else ""
4017        row_format_after = self.sql(expression, "row_format_after")
4018        row_format_after = f" {row_format_after}" if row_format_after else ""
4019        record_reader = self.sql(expression, "record_reader")
4020        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4021        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4022
4023    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4024        key_block_size = self.sql(expression, "key_block_size")
4025        if key_block_size:
4026            return f"KEY_BLOCK_SIZE = {key_block_size}"
4027
4028        using = self.sql(expression, "using")
4029        if using:
4030            return f"USING {using}"
4031
4032        parser = self.sql(expression, "parser")
4033        if parser:
4034            return f"WITH PARSER {parser}"
4035
4036        comment = self.sql(expression, "comment")
4037        if comment:
4038            return f"COMMENT {comment}"
4039
4040        visible = expression.args.get("visible")
4041        if visible is not None:
4042            return "VISIBLE" if visible else "INVISIBLE"
4043
4044        engine_attr = self.sql(expression, "engine_attr")
4045        if engine_attr:
4046            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4047
4048        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4049        if secondary_engine_attr:
4050            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4051
4052        self.unsupported("Unsupported index constraint option.")
4053        return ""
4054
4055    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4056        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4057        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4058
4059    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4060        kind = self.sql(expression, "kind")
4061        kind = f"{kind} INDEX" if kind else "INDEX"
4062        this = self.sql(expression, "this")
4063        this = f" {this}" if this else ""
4064        index_type = self.sql(expression, "index_type")
4065        index_type = f" USING {index_type}" if index_type else ""
4066        expressions = self.expressions(expression, flat=True)
4067        expressions = f" ({expressions})" if expressions else ""
4068        options = self.expressions(expression, key="options", sep=" ")
4069        options = f" {options}" if options else ""
4070        return f"{kind}{this}{index_type}{expressions}{options}"
4071
4072    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4073        if self.NVL2_SUPPORTED:
4074            return self.function_fallback_sql(expression)
4075
4076        case = exp.Case().when(
4077            expression.this.is_(exp.null()).not_(copy=False),
4078            expression.args["true"],
4079            copy=False,
4080        )
4081        else_cond = expression.args.get("false")
4082        if else_cond:
4083            case.else_(else_cond, copy=False)
4084
4085        return self.sql(case)
4086
4087    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4088        this = self.sql(expression, "this")
4089        expr = self.sql(expression, "expression")
4090        iterator = self.sql(expression, "iterator")
4091        condition = self.sql(expression, "condition")
4092        condition = f" IF {condition}" if condition else ""
4093        return f"{this} FOR {expr} IN {iterator}{condition}"
4094
4095    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4096        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4097
4098    def opclass_sql(self, expression: exp.Opclass) -> str:
4099        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4100
4101    def predict_sql(self, expression: exp.Predict) -> str:
4102        model = self.sql(expression, "this")
4103        model = f"MODEL {model}"
4104        table = self.sql(expression, "expression")
4105        table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4106        parameters = self.sql(expression, "params_struct")
4107        return self.func("PREDICT", model, table, parameters or None)
4108
4109    def forin_sql(self, expression: exp.ForIn) -> str:
4110        this = self.sql(expression, "this")
4111        expression_sql = self.sql(expression, "expression")
4112        return f"FOR {this} DO {expression_sql}"
4113
4114    def refresh_sql(self, expression: exp.Refresh) -> str:
4115        this = self.sql(expression, "this")
4116        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4117        return f"REFRESH {table}{this}"
4118
4119    def toarray_sql(self, expression: exp.ToArray) -> str:
4120        arg = expression.this
4121        if not arg.type:
4122            from sqlglot.optimizer.annotate_types import annotate_types
4123
4124            arg = annotate_types(arg, dialect=self.dialect)
4125
4126        if arg.is_type(exp.DataType.Type.ARRAY):
4127            return self.sql(arg)
4128
4129        cond_for_null = arg.is_(exp.null())
4130        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4131
4132    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4133        this = expression.this
4134        time_format = self.format_time(expression)
4135
4136        if time_format:
4137            return self.sql(
4138                exp.cast(
4139                    exp.StrToTime(this=this, format=expression.args["format"]),
4140                    exp.DataType.Type.TIME,
4141                )
4142            )
4143
4144        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4145            return self.sql(this)
4146
4147        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4148
4149    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4150        this = expression.this
4151        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4152            return self.sql(this)
4153
4154        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4155
4156    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4157        this = expression.this
4158        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4159            return self.sql(this)
4160
4161        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4162
4163    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4164        this = expression.this
4165        time_format = self.format_time(expression)
4166
4167        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4168            return self.sql(
4169                exp.cast(
4170                    exp.StrToTime(this=this, format=expression.args["format"]),
4171                    exp.DataType.Type.DATE,
4172                )
4173            )
4174
4175        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4176            return self.sql(this)
4177
4178        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4179
4180    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4181        return self.sql(
4182            exp.func(
4183                "DATEDIFF",
4184                expression.this,
4185                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4186                "day",
4187            )
4188        )
4189
4190    def lastday_sql(self, expression: exp.LastDay) -> str:
4191        if self.LAST_DAY_SUPPORTS_DATE_PART:
4192            return self.function_fallback_sql(expression)
4193
4194        unit = expression.text("unit")
4195        if unit and unit != "MONTH":
4196            self.unsupported("Date parts are not supported in LAST_DAY.")
4197
4198        return self.func("LAST_DAY", expression.this)
4199
4200    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4201        from sqlglot.dialects.dialect import unit_to_str
4202
4203        return self.func(
4204            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4205        )
4206
4207    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4208        if self.CAN_IMPLEMENT_ARRAY_ANY:
4209            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4210            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4211            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4212            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4213
4214        from sqlglot.dialects import Dialect
4215
4216        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4217        if self.dialect.__class__ != Dialect:
4218            self.unsupported("ARRAY_ANY is unsupported")
4219
4220        return self.function_fallback_sql(expression)
4221
4222    def struct_sql(self, expression: exp.Struct) -> str:
4223        expression.set(
4224            "expressions",
4225            [
4226                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4227                if isinstance(e, exp.PropertyEQ)
4228                else e
4229                for e in expression.expressions
4230            ],
4231        )
4232
4233        return self.function_fallback_sql(expression)
4234
4235    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4236        low = self.sql(expression, "this")
4237        high = self.sql(expression, "expression")
4238
4239        return f"{low} TO {high}"
4240
4241    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4242        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4243        tables = f" {self.expressions(expression)}"
4244
4245        exists = " IF EXISTS" if expression.args.get("exists") else ""
4246
4247        on_cluster = self.sql(expression, "cluster")
4248        on_cluster = f" {on_cluster}" if on_cluster else ""
4249
4250        identity = self.sql(expression, "identity")
4251        identity = f" {identity} IDENTITY" if identity else ""
4252
4253        option = self.sql(expression, "option")
4254        option = f" {option}" if option else ""
4255
4256        partition = self.sql(expression, "partition")
4257        partition = f" {partition}" if partition else ""
4258
4259        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4260
4261    # This transpiles T-SQL's CONVERT function
4262    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4263    def convert_sql(self, expression: exp.Convert) -> str:
4264        to = expression.this
4265        value = expression.expression
4266        style = expression.args.get("style")
4267        safe = expression.args.get("safe")
4268        strict = expression.args.get("strict")
4269
4270        if not to or not value:
4271            return ""
4272
4273        # Retrieve length of datatype and override to default if not specified
4274        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4275            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4276
4277        transformed: t.Optional[exp.Expression] = None
4278        cast = exp.Cast if strict else exp.TryCast
4279
4280        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4281        if isinstance(style, exp.Literal) and style.is_int:
4282            from sqlglot.dialects.tsql import TSQL
4283
4284            style_value = style.name
4285            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4286            if not converted_style:
4287                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4288
4289            fmt = exp.Literal.string(converted_style)
4290
4291            if to.this == exp.DataType.Type.DATE:
4292                transformed = exp.StrToDate(this=value, format=fmt)
4293            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4294                transformed = exp.StrToTime(this=value, format=fmt)
4295            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4296                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4297            elif to.this == exp.DataType.Type.TEXT:
4298                transformed = exp.TimeToStr(this=value, format=fmt)
4299
4300        if not transformed:
4301            transformed = cast(this=value, to=to, safe=safe)
4302
4303        return self.sql(transformed)
4304
4305    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4306        this = expression.this
4307        if isinstance(this, exp.JSONPathWildcard):
4308            this = self.json_path_part(this)
4309            return f".{this}" if this else ""
4310
4311        if exp.SAFE_IDENTIFIER_RE.match(this):
4312            return f".{this}"
4313
4314        this = self.json_path_part(this)
4315        return (
4316            f"[{this}]"
4317            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4318            else f".{this}"
4319        )
4320
4321    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4322        this = self.json_path_part(expression.this)
4323        return f"[{this}]" if this else ""
4324
4325    def _simplify_unless_literal(self, expression: E) -> E:
4326        if not isinstance(expression, exp.Literal):
4327            from sqlglot.optimizer.simplify import simplify
4328
4329            expression = simplify(expression, dialect=self.dialect)
4330
4331        return expression
4332
4333    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4334        this = expression.this
4335        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4336            self.unsupported(
4337                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4338            )
4339            return self.sql(this)
4340
4341        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4342            # The first modifier here will be the one closest to the AggFunc's arg
4343            mods = sorted(
4344                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4345                key=lambda x: 0
4346                if isinstance(x, exp.HavingMax)
4347                else (1 if isinstance(x, exp.Order) else 2),
4348            )
4349
4350            if mods:
4351                mod = mods[0]
4352                this = expression.__class__(this=mod.this.copy())
4353                this.meta["inline"] = True
4354                mod.this.replace(this)
4355                return self.sql(expression.this)
4356
4357            agg_func = expression.find(exp.AggFunc)
4358
4359            if agg_func:
4360                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4361                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4362
4363        return f"{self.sql(expression, 'this')} {text}"
4364
4365    def _replace_line_breaks(self, string: str) -> str:
4366        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4367        if self.pretty:
4368            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4369        return string
4370
4371    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4372        option = self.sql(expression, "this")
4373
4374        if expression.expressions:
4375            upper = option.upper()
4376
4377            # Snowflake FILE_FORMAT options are separated by whitespace
4378            sep = " " if upper == "FILE_FORMAT" else ", "
4379
4380            # Databricks copy/format options do not set their list of values with EQ
4381            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4382            values = self.expressions(expression, flat=True, sep=sep)
4383            return f"{option}{op}({values})"
4384
4385        value = self.sql(expression, "expression")
4386
4387        if not value:
4388            return option
4389
4390        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4391
4392        return f"{option}{op}{value}"
4393
4394    def credentials_sql(self, expression: exp.Credentials) -> str:
4395        cred_expr = expression.args.get("credentials")
4396        if isinstance(cred_expr, exp.Literal):
4397            # Redshift case: CREDENTIALS <string>
4398            credentials = self.sql(expression, "credentials")
4399            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4400        else:
4401            # Snowflake case: CREDENTIALS = (...)
4402            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4403            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4404
4405        storage = self.sql(expression, "storage")
4406        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4407
4408        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4409        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4410
4411        iam_role = self.sql(expression, "iam_role")
4412        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4413
4414        region = self.sql(expression, "region")
4415        region = f" REGION {region}" if region else ""
4416
4417        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4418
4419    def copy_sql(self, expression: exp.Copy) -> str:
4420        this = self.sql(expression, "this")
4421        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4422
4423        credentials = self.sql(expression, "credentials")
4424        credentials = self.seg(credentials) if credentials else ""
4425        kind = self.seg("FROM" if expression.args.get("kind") else "TO")
4426        files = self.expressions(expression, key="files", flat=True)
4427
4428        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4429        params = self.expressions(
4430            expression,
4431            key="params",
4432            sep=sep,
4433            new_line=True,
4434            skip_last=True,
4435            skip_first=True,
4436            indent=self.COPY_PARAMS_ARE_WRAPPED,
4437        )
4438
4439        if params:
4440            if self.COPY_PARAMS_ARE_WRAPPED:
4441                params = f" WITH ({params})"
4442            elif not self.pretty:
4443                params = f" {params}"
4444
4445        return f"COPY{this}{kind} {files}{credentials}{params}"
4446
4447    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4448        return ""
4449
4450    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4451        on_sql = "ON" if expression.args.get("on") else "OFF"
4452        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4453        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4454        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4455        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4456
4457        if filter_col or retention_period:
4458            on_sql = self.func("ON", filter_col, retention_period)
4459
4460        return f"DATA_DELETION={on_sql}"
4461
4462    def maskingpolicycolumnconstraint_sql(
4463        self, expression: exp.MaskingPolicyColumnConstraint
4464    ) -> str:
4465        this = self.sql(expression, "this")
4466        expressions = self.expressions(expression, flat=True)
4467        expressions = f" USING ({expressions})" if expressions else ""
4468        return f"MASKING POLICY {this}{expressions}"
4469
4470    def gapfill_sql(self, expression: exp.GapFill) -> str:
4471        this = self.sql(expression, "this")
4472        this = f"TABLE {this}"
4473        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4474
4475    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4476        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4477
4478    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4479        this = self.sql(expression, "this")
4480        expr = expression.expression
4481
4482        if isinstance(expr, exp.Func):
4483            # T-SQL's CLR functions are case sensitive
4484            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4485        else:
4486            expr = self.sql(expression, "expression")
4487
4488        return self.scope_resolution(expr, this)
4489
4490    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4491        if self.PARSE_JSON_NAME is None:
4492            return self.sql(expression.this)
4493
4494        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4495
4496    def rand_sql(self, expression: exp.Rand) -> str:
4497        lower = self.sql(expression, "lower")
4498        upper = self.sql(expression, "upper")
4499
4500        if lower and upper:
4501            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4502        return self.func("RAND", expression.this)
4503
4504    def changes_sql(self, expression: exp.Changes) -> str:
4505        information = self.sql(expression, "information")
4506        information = f"INFORMATION => {information}"
4507        at_before = self.sql(expression, "at_before")
4508        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4509        end = self.sql(expression, "end")
4510        end = f"{self.seg('')}{end}" if end else ""
4511
4512        return f"CHANGES ({information}){at_before}{end}"
4513
4514    def pad_sql(self, expression: exp.Pad) -> str:
4515        prefix = "L" if expression.args.get("is_left") else "R"
4516
4517        fill_pattern = self.sql(expression, "fill_pattern") or None
4518        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4519            fill_pattern = "' '"
4520
4521        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4522
4523    def summarize_sql(self, expression: exp.Summarize) -> str:
4524        table = " TABLE" if expression.args.get("table") else ""
4525        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4526
4527    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4528        generate_series = exp.GenerateSeries(**expression.args)
4529
4530        parent = expression.parent
4531        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4532            parent = parent.parent
4533
4534        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4535            return self.sql(exp.Unnest(expressions=[generate_series]))
4536
4537        if isinstance(parent, exp.Select):
4538            self.unsupported("GenerateSeries projection unnesting is not supported.")
4539
4540        return self.sql(generate_series)
4541
4542    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4543        exprs = expression.expressions
4544        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4545            rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4546        else:
4547            rhs = self.expressions(expression)
4548
4549        return self.func(name, expression.this, rhs or None)
4550
4551    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4552        if self.SUPPORTS_CONVERT_TIMEZONE:
4553            return self.function_fallback_sql(expression)
4554
4555        source_tz = expression.args.get("source_tz")
4556        target_tz = expression.args.get("target_tz")
4557        timestamp = expression.args.get("timestamp")
4558
4559        if source_tz and timestamp:
4560            timestamp = exp.AtTimeZone(
4561                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4562            )
4563
4564        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4565
4566        return self.sql(expr)
4567
4568    def json_sql(self, expression: exp.JSON) -> str:
4569        this = self.sql(expression, "this")
4570        this = f" {this}" if this else ""
4571
4572        _with = expression.args.get("with")
4573
4574        if _with is None:
4575            with_sql = ""
4576        elif not _with:
4577            with_sql = " WITHOUT"
4578        else:
4579            with_sql = " WITH"
4580
4581        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4582
4583        return f"JSON{this}{with_sql}{unique_sql}"
4584
4585    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4586        def _generate_on_options(arg: t.Any) -> str:
4587            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4588
4589        path = self.sql(expression, "path")
4590        returning = self.sql(expression, "returning")
4591        returning = f" RETURNING {returning}" if returning else ""
4592
4593        on_condition = self.sql(expression, "on_condition")
4594        on_condition = f" {on_condition}" if on_condition else ""
4595
4596        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4597
4598    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4599        else_ = "ELSE " if expression.args.get("else_") else ""
4600        condition = self.sql(expression, "expression")
4601        condition = f"WHEN {condition} THEN " if condition else else_
4602        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4603        return f"{condition}{insert}"
4604
4605    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4606        kind = self.sql(expression, "kind")
4607        expressions = self.seg(self.expressions(expression, sep=" "))
4608        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4609        return res
4610
4611    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4612        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4613        empty = expression.args.get("empty")
4614        empty = (
4615            f"DEFAULT {empty} ON EMPTY"
4616            if isinstance(empty, exp.Expression)
4617            else self.sql(expression, "empty")
4618        )
4619
4620        error = expression.args.get("error")
4621        error = (
4622            f"DEFAULT {error} ON ERROR"
4623            if isinstance(error, exp.Expression)
4624            else self.sql(expression, "error")
4625        )
4626
4627        if error and empty:
4628            error = (
4629                f"{empty} {error}"
4630                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4631                else f"{error} {empty}"
4632            )
4633            empty = ""
4634
4635        null = self.sql(expression, "null")
4636
4637        return f"{empty}{error}{null}"
4638
4639    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4640        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4641        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
4642
4643    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4644        this = self.sql(expression, "this")
4645        path = self.sql(expression, "path")
4646
4647        passing = self.expressions(expression, "passing")
4648        passing = f" PASSING {passing}" if passing else ""
4649
4650        on_condition = self.sql(expression, "on_condition")
4651        on_condition = f" {on_condition}" if on_condition else ""
4652
4653        path = f"{path}{passing}{on_condition}"
4654
4655        return self.func("JSON_EXISTS", this, path)
4656
4657    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4658        array_agg = self.function_fallback_sql(expression)
4659
4660        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4661        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4662        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4663            parent = expression.parent
4664            if isinstance(parent, exp.Filter):
4665                parent_cond = parent.expression.this
4666                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4667            else:
4668                this = expression.this
4669                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4670                if this.find(exp.Column):
4671                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4672                    this_sql = (
4673                        self.expressions(this)
4674                        if isinstance(this, exp.Distinct)
4675                        else self.sql(expression, "this")
4676                    )
4677
4678                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4679
4680        return array_agg
4681
4682    def apply_sql(self, expression: exp.Apply) -> str:
4683        this = self.sql(expression, "this")
4684        expr = self.sql(expression, "expression")
4685
4686        return f"{this} APPLY({expr})"
4687
4688    def grant_sql(self, expression: exp.Grant) -> str:
4689        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4690
4691        kind = self.sql(expression, "kind")
4692        kind = f" {kind}" if kind else ""
4693
4694        securable = self.sql(expression, "securable")
4695        securable = f" {securable}" if securable else ""
4696
4697        principals = self.expressions(expression, key="principals", flat=True)
4698
4699        grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4700
4701        return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4702
4703    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4704        this = self.sql(expression, "this")
4705        columns = self.expressions(expression, flat=True)
4706        columns = f"({columns})" if columns else ""
4707
4708        return f"{this}{columns}"
4709
4710    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4711        this = self.sql(expression, "this")
4712
4713        kind = self.sql(expression, "kind")
4714        kind = f"{kind} " if kind else ""
4715
4716        return f"{kind}{this}"
4717
4718    def columns_sql(self, expression: exp.Columns):
4719        func = self.function_fallback_sql(expression)
4720        if expression.args.get("unpack"):
4721            func = f"*{func}"
4722
4723        return func
4724
4725    def overlay_sql(self, expression: exp.Overlay):
4726        this = self.sql(expression, "this")
4727        expr = self.sql(expression, "expression")
4728        from_sql = self.sql(expression, "from")
4729        for_sql = self.sql(expression, "for")
4730        for_sql = f" FOR {for_sql}" if for_sql else ""
4731
4732        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
4733
4734    @unsupported_args("format")
4735    def todouble_sql(self, expression: exp.ToDouble) -> str:
4736        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4737
4738    def string_sql(self, expression: exp.String) -> str:
4739        this = expression.this
4740        zone = expression.args.get("zone")
4741
4742        if zone:
4743            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4744            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4745            # set for source_tz to transpile the time conversion before the STRING cast
4746            this = exp.ConvertTimezone(
4747                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4748            )
4749
4750        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
4751
4752    def median_sql(self, expression: exp.Median):
4753        if not self.SUPPORTS_MEDIAN:
4754            return self.sql(
4755                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4756            )
4757
4758        return self.function_fallback_sql(expression)
4759
4760    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4761        filler = self.sql(expression, "this")
4762        filler = f" {filler}" if filler else ""
4763        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4764        return f"TRUNCATE{filler} {with_count}"
4765
4766    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
4767        if self.SUPPORTS_UNIX_SECONDS:
4768            return self.function_fallback_sql(expression)
4769
4770        start_ts = exp.cast(
4771            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
4772        )
4773
4774        return self.sql(
4775            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
4776        )
4777
4778    def arraysize_sql(self, expression: exp.ArraySize) -> str:
4779        dim = expression.expression
4780
4781        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
4782        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
4783            if not (dim.is_int and dim.name == "1"):
4784                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
4785            dim = None
4786
4787        # If dimension is required but not specified, default initialize it
4788        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
4789            dim = exp.Literal.number(1)
4790
4791        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4792
4793    def attach_sql(self, expression: exp.Attach) -> str:
4794        this = self.sql(expression, "this")
4795        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
4796        expressions = self.expressions(expression)
4797        expressions = f" ({expressions})" if expressions else ""
4798
4799        return f"ATTACH{exists_sql} {this}{expressions}"
4800
4801    def detach_sql(self, expression: exp.Detach) -> str:
4802        this = self.sql(expression, "this")
4803        # the DATABASE keyword is required if IF EXISTS is set
4804        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
4805        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
4806        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
4807
4808        return f"DETACH{exists_sql} {this}"
4809
4810    def attachoption_sql(self, expression: exp.AttachOption) -> str:
4811        this = self.sql(expression, "this")
4812        value = self.sql(expression, "expression")
4813        value = f" {value}" if value else ""
4814        return f"{this}{value}"
4815
4816    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4817        this_sql = self.sql(expression, "this")
4818        if isinstance(expression.this, exp.Table):
4819            this_sql = f"TABLE {this_sql}"
4820
4821        return self.func(
4822            "FEATURES_AT_TIME",
4823            this_sql,
4824            expression.args.get("time"),
4825            expression.args.get("num_rows"),
4826            expression.args.get("ignore_feature_nulls"),
4827        )
4828
4829    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4830        return (
4831            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
4832        )
4833
4834    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
4835        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
4836        encode = f"{encode} {self.sql(expression, 'this')}"
4837
4838        properties = expression.args.get("properties")
4839        if properties:
4840            encode = f"{encode} {self.properties(properties)}"
4841
4842        return encode
4843
4844    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
4845        this = self.sql(expression, "this")
4846        include = f"INCLUDE {this}"
4847
4848        column_def = self.sql(expression, "column_def")
4849        if column_def:
4850            include = f"{include} {column_def}"
4851
4852        alias = self.sql(expression, "alias")
4853        if alias:
4854            include = f"{include} AS {alias}"
4855
4856        return include
4857
4858    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
4859        name = f"NAME {self.sql(expression, 'this')}"
4860        return self.func("XMLELEMENT", name, *expression.expressions)
4861
4862    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
4863        this = self.sql(expression, "this")
4864        expr = self.sql(expression, "expression")
4865        expr = f"({expr})" if expr else ""
4866        return f"{this}{expr}"
4867
4868    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
4869        partitions = self.expressions(expression, "partition_expressions")
4870        create = self.expressions(expression, "create_expressions")
4871        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
4872
4873    def partitionbyrangepropertydynamic_sql(
4874        self, expression: exp.PartitionByRangePropertyDynamic
4875    ) -> str:
4876        start = self.sql(expression, "start")
4877        end = self.sql(expression, "end")
4878
4879        every = expression.args["every"]
4880        if isinstance(every, exp.Interval) and every.this.is_string:
4881            every.this.replace(exp.Literal.number(every.name))
4882
4883        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4884
4885    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
4886        name = self.sql(expression, "this")
4887        values = self.expressions(expression, flat=True)
4888
4889        return f"NAME {name} VALUE {values}"
4890
4891    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
4892        kind = self.sql(expression, "kind")
4893        sample = self.sql(expression, "sample")
4894        return f"SAMPLE {sample} {kind}"
4895
4896    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
4897        kind = self.sql(expression, "kind")
4898        option = self.sql(expression, "option")
4899        option = f" {option}" if option else ""
4900        this = self.sql(expression, "this")
4901        this = f" {this}" if this else ""
4902        columns = self.expressions(expression)
4903        columns = f" {columns}" if columns else ""
4904        return f"{kind}{option} STATISTICS{this}{columns}"
4905
4906    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
4907        this = self.sql(expression, "this")
4908        columns = self.expressions(expression)
4909        inner_expression = self.sql(expression, "expression")
4910        inner_expression = f" {inner_expression}" if inner_expression else ""
4911        update_options = self.sql(expression, "update_options")
4912        update_options = f" {update_options} UPDATE" if update_options else ""
4913        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
4914
4915    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
4916        kind = self.sql(expression, "kind")
4917        kind = f" {kind}" if kind else ""
4918        return f"DELETE{kind} STATISTICS"
4919
4920    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
4921        inner_expression = self.sql(expression, "expression")
4922        return f"LIST CHAINED ROWS{inner_expression}"
4923
4924    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
4925        kind = self.sql(expression, "kind")
4926        this = self.sql(expression, "this")
4927        this = f" {this}" if this else ""
4928        inner_expression = self.sql(expression, "expression")
4929        return f"VALIDATE {kind}{this}{inner_expression}"
4930
4931    def analyze_sql(self, expression: exp.Analyze) -> str:
4932        options = self.expressions(expression, key="options", sep=" ")
4933        options = f" {options}" if options else ""
4934        kind = self.sql(expression, "kind")
4935        kind = f" {kind}" if kind else ""
4936        this = self.sql(expression, "this")
4937        this = f" {this}" if this else ""
4938        mode = self.sql(expression, "mode")
4939        mode = f" {mode}" if mode else ""
4940        properties = self.sql(expression, "properties")
4941        properties = f" {properties}" if properties else ""
4942        partition = self.sql(expression, "partition")
4943        partition = f" {partition}" if partition else ""
4944        inner_expression = self.sql(expression, "expression")
4945        inner_expression = f" {inner_expression}" if inner_expression else ""
4946        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4947
4948    def xmltable_sql(self, expression: exp.XMLTable) -> str:
4949        this = self.sql(expression, "this")
4950        namespaces = self.expressions(expression, key="namespaces")
4951        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
4952        passing = self.expressions(expression, key="passing")
4953        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
4954        columns = self.expressions(expression, key="columns")
4955        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
4956        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4957        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4958
4959    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4960        this = self.sql(expression, "this")
4961        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
4962
4963    def export_sql(self, expression: exp.Export) -> str:
4964        this = self.sql(expression, "this")
4965        connection = self.sql(expression, "connection")
4966        connection = f"WITH CONNECTION {connection} " if connection else ""
4967        options = self.sql(expression, "options")
4968        return f"EXPORT DATA {connection}{options} AS {this}"
4969
4970    def declare_sql(self, expression: exp.Declare) -> str:
4971        return f"DECLARE {self.expressions(expression, flat=True)}"
4972
4973    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
4974        variable = self.sql(expression, "this")
4975        default = self.sql(expression, "default")
4976        default = f" = {default}" if default else ""
4977
4978        kind = self.sql(expression, "kind")
4979        if isinstance(expression.args.get("kind"), exp.Schema):
4980            kind = f"TABLE {kind}"
4981
4982        return f"{variable} AS {kind}{default}"
4983
4984    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
4985        kind = self.sql(expression, "kind")
4986        this = self.sql(expression, "this")
4987        set = self.sql(expression, "expression")
4988        using = self.sql(expression, "using")
4989        using = f" USING {using}" if using else ""
4990
4991        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
4992
4993        return f"{kind_sql} {this} SET {set}{using}"
4994
4995    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
4996        params = self.expressions(expression, key="params", flat=True)
4997        return self.func(expression.name, *expression.expressions) + f"({params})"
4998
4999    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5000        return self.func(expression.name, *expression.expressions)
5001
5002    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5003        return self.anonymousaggfunc_sql(expression)
5004
5005    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5006        return self.parameterizedagg_sql(expression)
5007
5008    def show_sql(self, expression: exp.Show) -> str:
5009        self.unsupported("Unsupported SHOW statement")
5010        return ""
5011
5012    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5013        # Snowflake GET/PUT statements:
5014        #   PUT <file> <internalStage> <properties>
5015        #   GET <internalStage> <file> <properties>
5016        props = expression.args.get("properties")
5017        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5018        this = self.sql(expression, "this")
5019        target = self.sql(expression, "target")
5020
5021        if isinstance(expression, exp.Put):
5022            return f"PUT {this} {target}{props_sql}"
5023        else:
5024            return f"GET {target} {this}{props_sql}"
5025
5026    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5027        this = self.sql(expression, "this")
5028        expr = self.sql(expression, "expression")
5029        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5030        return f"TRANSLATE({this} USING {expr}{with_error})"
5031
5032    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5033        if self.SUPPORTS_DECODE_CASE:
5034            return self.func("DECODE", *expression.expressions)
5035
5036        expression, *expressions = expression.expressions
5037
5038        ifs = []
5039        for search, result in zip(expressions[::2], expressions[1::2]):
5040            if isinstance(search, exp.Literal):
5041                ifs.append(exp.If(this=expression.eq(search), true=result))
5042            elif isinstance(search, exp.Null):
5043                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5044            else:
5045                if isinstance(search, exp.Binary):
5046                    search = exp.paren(search)
5047
5048                cond = exp.or_(
5049                    expression.eq(search),
5050                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5051                    copy=False,
5052                )
5053                ifs.append(exp.If(this=cond, true=result))
5054
5055        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5056        return self.sql(case)
5057
5058    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5059        this = self.sql(expression, "this")
5060        this = self.seg(this, sep="")
5061        dimensions = self.expressions(
5062            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5063        )
5064        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5065        metrics = self.expressions(
5066            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5067        )
5068        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5069        where = self.sql(expression, "where")
5070        where = self.seg(f"WHERE {where}") if where else ""
5071        return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"

Generator converts a given syntax tree to the corresponding SQL string.

Arguments:
  • pretty: Whether to format the produced SQL string. Default: False.
  • identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
  • normalize: Whether to normalize identifiers to lowercase. Default: False.
  • pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
  • indent: The indentation size in a formatted string. For example, this affects the indentation of subqueries and filters under a WHERE clause. Default: 2.
  • normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
  • unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
  • leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
  • max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
  • comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
713    def __init__(
714        self,
715        pretty: t.Optional[bool] = None,
716        identify: str | bool = False,
717        normalize: bool = False,
718        pad: int = 2,
719        indent: int = 2,
720        normalize_functions: t.Optional[str | bool] = None,
721        unsupported_level: ErrorLevel = ErrorLevel.WARN,
722        max_unsupported: int = 3,
723        leading_comma: bool = False,
724        max_text_width: int = 80,
725        comments: bool = True,
726        dialect: DialectType = None,
727    ):
728        import sqlglot
729        from sqlglot.dialects import Dialect
730
731        self.pretty = pretty if pretty is not None else sqlglot.pretty
732        self.identify = identify
733        self.normalize = normalize
734        self.pad = pad
735        self._indent = indent
736        self.unsupported_level = unsupported_level
737        self.max_unsupported = max_unsupported
738        self.leading_comma = leading_comma
739        self.max_text_width = max_text_width
740        self.comments = comments
741        self.dialect = Dialect.get_or_raise(dialect)
742
743        # This is both a Dialect property and a Generator argument, so we prioritize the latter
744        self.normalize_functions = (
745            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
746        )
747
748        self.unsupported_messages: t.List[str] = []
749        self._escaped_quote_end: str = (
750            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
751        )
752        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
753
754        self._next_name = name_sequence("_t")
755
756        self._identifier_start = self.dialect.IDENTIFIER_START
757        self._identifier_end = self.dialect.IDENTIFIER_END
758
759        self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] = {<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
NULL_ORDERING_SUPPORTED: Optional[bool] = True
IGNORE_NULLS_IN_FUNC = False
LOCKING_READS_SUPPORTED = False
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
WRAP_DERIVED_VALUES = True
CREATE_FUNCTION_RETURN_AS = True
MATCHED_BY_SOURCE = True
SINGLE_STRING_INTERVAL = False
INTERVAL_ALLOWS_PLURAL_FORM = True
LIMIT_FETCH = 'ALL'
LIMIT_ONLY_LITERALS = False
RENAME_TABLE_WITH_DB = True
GROUPINGS_SEP = ','
INDEX_ON = 'ON'
JOIN_HINTS = True
TABLE_HINTS = True
QUERY_HINTS = True
QUERY_HINT_SEP = ', '
IS_BOOL_ALLOWED = True
DUPLICATE_KEY_UPDATE_WITH_SET = True
LIMIT_IS_TOP = False
RETURNING_END = True
EXTRACT_ALLOWS_QUOTES = True
TZ_TO_WITH_TIME_ZONE = False
NVL2_SUPPORTED = True
SELECT_KINDS: Tuple[str, ...] = ('STRUCT', 'VALUE')
VALUES_AS_TABLE = True
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
UNNEST_WITH_ORDINALITY = True
AGGREGATE_FILTER_SUPPORTED = True
SEMI_ANTI_JOIN_WITH_SIDE = True
COMPUTED_COLUMN_WITH_TYPE = True
SUPPORTS_TABLE_COPY = True
TABLESAMPLE_REQUIRES_PARENS = True
TABLESAMPLE_SIZE_IS_ROWS = True
TABLESAMPLE_KEYWORDS = 'TABLESAMPLE'
TABLESAMPLE_WITH_METHOD = True
TABLESAMPLE_SEED_KEYWORD = 'SEED'
COLLATE_IS_FUNC = False
DATA_TYPE_SPECIFIERS_ALLOWED = False
ENSURE_BOOLS = False
CTE_RECURSIVE_KEYWORD_REQUIRED = True
SUPPORTS_SINGLE_ARG_CONCAT = True
LAST_DAY_SUPPORTS_DATE_PART = True
SUPPORTS_TABLE_ALIAS_COLUMNS = True
UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
JSON_KEY_VALUE_PAIR_SEP = ':'
INSERT_OVERWRITE = ' OVERWRITE TABLE'
SUPPORTS_SELECT_INTO = False
SUPPORTS_UNLOGGED_TABLES = False
SUPPORTS_CREATE_TABLE_LIKE = True
LIKE_PROPERTY_INSIDE_SCHEMA = False
MULTI_ARG_DISTINCT = True
JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
JSON_PATH_BRACKETED_KEY_SUPPORTED = True
JSON_PATH_SINGLE_QUOTE_ESCAPE = False
CAN_IMPLEMENT_ARRAY_ANY = False
SUPPORTS_TO_NUMBER = True
SUPPORTS_WINDOW_EXCLUDE = False
SET_OP_MODIFIERS = True
COPY_PARAMS_ARE_WRAPPED = True
COPY_PARAMS_EQ_REQUIRED = False
COPY_HAS_INTO_KEYWORD = True
TRY_SUPPORTED = True
SUPPORTS_UESCAPE = True
STAR_EXCEPT = 'EXCEPT'
HEX_FUNC = 'HEX'
WITH_PROPERTIES_PREFIX = 'WITH'
QUOTE_JSON_PATH = True
PAD_FILL_PATTERN_IS_REQUIRED = False
SUPPORTS_EXPLODING_PROJECTIONS = True
ARRAY_CONCAT_IS_VAR_LEN = True
SUPPORTS_CONVERT_TIMEZONE = False
SUPPORTS_MEDIAN = True
SUPPORTS_UNIX_SECONDS = False
ALTER_SET_WRAPPED = False
NORMALIZE_EXTRACT_DATE_PARTS = False
PARSE_JSON_NAME: Optional[str] = 'PARSE_JSON'
ARRAY_SIZE_NAME: str = 'ARRAY_LENGTH'
ALTER_SET_TYPE = 'SET DATA TYPE'
ARRAY_SIZE_DIM_REQUIRED: Optional[bool] = None
SUPPORTS_DECODE_CASE = True
TYPE_MAPPING = {<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS = {'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS = {'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
TOKEN_MAPPING: Dict[sqlglot.tokens.TokenType, str] = {}
STRUCT_DELIMITER = ('<', '>')
PARAMETER_TOKEN = '@'
NAMED_PLACEHOLDER_TOKEN = ':'
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: Set[str] = set()
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: Set[str] = set()
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES = {<Type.VARCHAR: 'VARCHAR'>, <Type.NCHAR: 'NCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NVARCHAR: 'NVARCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: Set[Type[sqlglot.expressions.Expression]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: Tuple[Type[sqlglot.expressions.Expression], ...] = ()
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: List[str]
def generate( self, expression: sqlglot.expressions.Expression, copy: bool = True) -> str:
761    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
762        """
763        Generates the SQL string corresponding to the given syntax tree.
764
765        Args:
766            expression: The syntax tree.
767            copy: Whether to copy the expression. The generator performs mutations so
768                it is safer to copy.
769
770        Returns:
771            The SQL string corresponding to `expression`.
772        """
773        if copy:
774            expression = expression.copy()
775
776        expression = self.preprocess(expression)
777
778        self.unsupported_messages = []
779        sql = self.sql(expression).strip()
780
781        if self.pretty:
782            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
783
784        if self.unsupported_level == ErrorLevel.IGNORE:
785            return sql
786
787        if self.unsupported_level == ErrorLevel.WARN:
788            for msg in self.unsupported_messages:
789                logger.warning(msg)
790        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
791            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
792
793        return sql

Generates the SQL string corresponding to the given syntax tree.

Arguments:
  • expression: The syntax tree.
  • copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:

The SQL string corresponding to expression.

def preprocess( self, expression: sqlglot.expressions.Expression) -> sqlglot.expressions.Expression:
795    def preprocess(self, expression: exp.Expression) -> exp.Expression:
796        """Apply generic preprocessing transformations to a given expression."""
797        expression = self._move_ctes_to_top_level(expression)
798
799        if self.ENSURE_BOOLS:
800            from sqlglot.transforms import ensure_bools
801
802            expression = ensure_bools(expression)
803
804        return expression

Apply generic preprocessing transformations to a given expression.

def unsupported(self, message: str) -> None:
817    def unsupported(self, message: str) -> None:
818        if self.unsupported_level == ErrorLevel.IMMEDIATE:
819            raise UnsupportedError(message)
820        self.unsupported_messages.append(message)
def sep(self, sep: str = ' ') -> str:
822    def sep(self, sep: str = " ") -> str:
823        return f"{sep.strip()}\n" if self.pretty else sep
def seg(self, sql: str, sep: str = ' ') -> str:
825    def seg(self, sql: str, sep: str = " ") -> str:
826        return f"{self.sep(sep)}{sql}"
def sanitize_comment(self, comment: str) -> str:
828    def sanitize_comment(self, comment: str) -> str:
829        comment = " " + comment if comment[0].strip() else comment
830        comment = comment + " " if comment[-1].strip() else comment
831
832        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
833            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
834            comment = comment.replace("*/", "* /")
835
836        return comment
def maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
838    def maybe_comment(
839        self,
840        sql: str,
841        expression: t.Optional[exp.Expression] = None,
842        comments: t.Optional[t.List[str]] = None,
843        separated: bool = False,
844    ) -> str:
845        comments = (
846            ((expression and expression.comments) if comments is None else comments)  # type: ignore
847            if self.comments
848            else None
849        )
850
851        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
852            return sql
853
854        comments_sql = " ".join(
855            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
856        )
857
858        if not comments_sql:
859            return sql
860
861        comments_sql = self._replace_line_breaks(comments_sql)
862
863        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
864            return (
865                f"{self.sep()}{comments_sql}{sql}"
866                if not sql or sql[0].isspace()
867                else f"{comments_sql}{self.sep()}{sql}"
868            )
869
870        return f"{sql} {comments_sql}"
def wrap(self, expression: sqlglot.expressions.Expression | str) -> str:
872    def wrap(self, expression: exp.Expression | str) -> str:
873        this_sql = (
874            self.sql(expression)
875            if isinstance(expression, exp.UNWRAPPED_QUERIES)
876            else self.sql(expression, "this")
877        )
878        if not this_sql:
879            return "()"
880
881        this_sql = self.indent(this_sql, level=1, pad=0)
882        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str:
884    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
885        original = self.identify
886        self.identify = False
887        result = func(*args, **kwargs)
888        self.identify = original
889        return result
def normalize_func(self, name: str) -> str:
891    def normalize_func(self, name: str) -> str:
892        if self.normalize_functions == "upper" or self.normalize_functions is True:
893            return name.upper()
894        if self.normalize_functions == "lower":
895            return name.lower()
896        return name
def indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
898    def indent(
899        self,
900        sql: str,
901        level: int = 0,
902        pad: t.Optional[int] = None,
903        skip_first: bool = False,
904        skip_last: bool = False,
905    ) -> str:
906        if not self.pretty or not sql:
907            return sql
908
909        pad = self.pad if pad is None else pad
910        lines = sql.split("\n")
911
912        return "\n".join(
913            (
914                line
915                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
916                else f"{' ' * (level * self._indent + pad)}{line}"
917            )
918            for i, line in enumerate(lines)
919        )
def sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
921    def sql(
922        self,
923        expression: t.Optional[str | exp.Expression],
924        key: t.Optional[str] = None,
925        comment: bool = True,
926    ) -> str:
927        if not expression:
928            return ""
929
930        if isinstance(expression, str):
931            return expression
932
933        if key:
934            value = expression.args.get(key)
935            if value:
936                return self.sql(value)
937            return ""
938
939        transform = self.TRANSFORMS.get(expression.__class__)
940
941        if callable(transform):
942            sql = transform(self, expression)
943        elif isinstance(expression, exp.Expression):
944            exp_handler_name = f"{expression.key}_sql"
945
946            if hasattr(self, exp_handler_name):
947                sql = getattr(self, exp_handler_name)(expression)
948            elif isinstance(expression, exp.Func):
949                sql = self.function_fallback_sql(expression)
950            elif isinstance(expression, exp.Property):
951                sql = self.property_sql(expression)
952            else:
953                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
954        else:
955            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
956
957        return self.maybe_comment(sql, expression) if self.comments and comment else sql
def uncache_sql(self, expression: sqlglot.expressions.Uncache) -> str:
959    def uncache_sql(self, expression: exp.Uncache) -> str:
960        table = self.sql(expression, "this")
961        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
962        return f"UNCACHE TABLE{exists_sql} {table}"
def cache_sql(self, expression: sqlglot.expressions.Cache) -> str:
964    def cache_sql(self, expression: exp.Cache) -> str:
965        lazy = " LAZY" if expression.args.get("lazy") else ""
966        table = self.sql(expression, "this")
967        options = expression.args.get("options")
968        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
969        sql = self.sql(expression, "expression")
970        sql = f" AS{self.sep()}{sql}" if sql else ""
971        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
972        return self.prepend_ctes(expression, sql)
def characterset_sql(self, expression: sqlglot.expressions.CharacterSet) -> str:
974    def characterset_sql(self, expression: exp.CharacterSet) -> str:
975        if isinstance(expression.parent, exp.Cast):
976            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
977        default = "DEFAULT " if expression.args.get("default") else ""
978        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
def column_parts(self, expression: sqlglot.expressions.Column) -> str:
980    def column_parts(self, expression: exp.Column) -> str:
981        return ".".join(
982            self.sql(part)
983            for part in (
984                expression.args.get("catalog"),
985                expression.args.get("db"),
986                expression.args.get("table"),
987                expression.args.get("this"),
988            )
989            if part
990        )
def column_sql(self, expression: sqlglot.expressions.Column) -> str:
992    def column_sql(self, expression: exp.Column) -> str:
993        join_mark = " (+)" if expression.args.get("join_mark") else ""
994
995        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
996            join_mark = ""
997            self.unsupported("Outer join syntax using the (+) operator is not supported.")
998
999        return f"{self.column_parts(expression)}{join_mark}"
def columnposition_sql(self, expression: sqlglot.expressions.ColumnPosition) -> str:
1001    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1002        this = self.sql(expression, "this")
1003        this = f" {this}" if this else ""
1004        position = self.sql(expression, "position")
1005        return f"{position}{this}"
def columndef_sql(self, expression: sqlglot.expressions.ColumnDef, sep: str = ' ') -> str:
1007    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1008        column = self.sql(expression, "this")
1009        kind = self.sql(expression, "kind")
1010        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1011        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1012        kind = f"{sep}{kind}" if kind else ""
1013        constraints = f" {constraints}" if constraints else ""
1014        position = self.sql(expression, "position")
1015        position = f" {position}" if position else ""
1016
1017        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1018            kind = ""
1019
1020        return f"{exists}{column}{kind}{constraints}{position}"
def columnconstraint_sql(self, expression: sqlglot.expressions.ColumnConstraint) -> str:
1022    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1023        this = self.sql(expression, "this")
1024        kind_sql = self.sql(expression, "kind").strip()
1025        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
def computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1027    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1028        this = self.sql(expression, "this")
1029        if expression.args.get("not_null"):
1030            persisted = " PERSISTED NOT NULL"
1031        elif expression.args.get("persisted"):
1032            persisted = " PERSISTED"
1033        else:
1034            persisted = ""
1035
1036        return f"AS {this}{persisted}"
def autoincrementcolumnconstraint_sql(self, _) -> str:
1038    def autoincrementcolumnconstraint_sql(self, _) -> str:
1039        return self.token_sql(TokenType.AUTO_INCREMENT)
def compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
1041    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1042        if isinstance(expression.this, list):
1043            this = self.wrap(self.expressions(expression, key="this", flat=True))
1044        else:
1045            this = self.sql(expression, "this")
1046
1047        return f"COMPRESS {this}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1049    def generatedasidentitycolumnconstraint_sql(
1050        self, expression: exp.GeneratedAsIdentityColumnConstraint
1051    ) -> str:
1052        this = ""
1053        if expression.this is not None:
1054            on_null = " ON NULL" if expression.args.get("on_null") else ""
1055            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1056
1057        start = expression.args.get("start")
1058        start = f"START WITH {start}" if start else ""
1059        increment = expression.args.get("increment")
1060        increment = f" INCREMENT BY {increment}" if increment else ""
1061        minvalue = expression.args.get("minvalue")
1062        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1063        maxvalue = expression.args.get("maxvalue")
1064        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1065        cycle = expression.args.get("cycle")
1066        cycle_sql = ""
1067
1068        if cycle is not None:
1069            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1070            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1071
1072        sequence_opts = ""
1073        if start or increment or cycle_sql:
1074            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1075            sequence_opts = f" ({sequence_opts.strip()})"
1076
1077        expr = self.sql(expression, "expression")
1078        expr = f"({expr})" if expr else "IDENTITY"
1079
1080        return f"GENERATED{this} AS {expr}{sequence_opts}"
def generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1082    def generatedasrowcolumnconstraint_sql(
1083        self, expression: exp.GeneratedAsRowColumnConstraint
1084    ) -> str:
1085        start = "START" if expression.args.get("start") else "END"
1086        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1087        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
1089    def periodforsystemtimeconstraint_sql(
1090        self, expression: exp.PeriodForSystemTimeConstraint
1091    ) -> str:
1092        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
def notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
1094    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1095        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
def primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
1097    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1098        desc = expression.args.get("desc")
1099        if desc is not None:
1100            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1101        options = self.expressions(expression, key="options", flat=True, sep=" ")
1102        options = f" {options}" if options else ""
1103        return f"PRIMARY KEY{options}"
def uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1105    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1106        this = self.sql(expression, "this")
1107        this = f" {this}" if this else ""
1108        index_type = expression.args.get("index_type")
1109        index_type = f" USING {index_type}" if index_type else ""
1110        on_conflict = self.sql(expression, "on_conflict")
1111        on_conflict = f" {on_conflict}" if on_conflict else ""
1112        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1113        options = self.expressions(expression, key="options", flat=True, sep=" ")
1114        options = f" {options}" if options else ""
1115        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def createable_sql( self, expression: sqlglot.expressions.Create, locations: DefaultDict) -> str:
1117    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1118        return self.sql(expression, "this")
def create_sql(self, expression: sqlglot.expressions.Create) -> str:
1120    def create_sql(self, expression: exp.Create) -> str:
1121        kind = self.sql(expression, "kind")
1122        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1123        properties = expression.args.get("properties")
1124        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1125
1126        this = self.createable_sql(expression, properties_locs)
1127
1128        properties_sql = ""
1129        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1130            exp.Properties.Location.POST_WITH
1131        ):
1132            properties_sql = self.sql(
1133                exp.Properties(
1134                    expressions=[
1135                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
1136                        *properties_locs[exp.Properties.Location.POST_WITH],
1137                    ]
1138                )
1139            )
1140
1141            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1142                properties_sql = self.sep() + properties_sql
1143            elif not self.pretty:
1144                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1145                properties_sql = f" {properties_sql}"
1146
1147        begin = " BEGIN" if expression.args.get("begin") else ""
1148        end = " END" if expression.args.get("end") else ""
1149
1150        expression_sql = self.sql(expression, "expression")
1151        if expression_sql:
1152            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1153
1154            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1155                postalias_props_sql = ""
1156                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1157                    postalias_props_sql = self.properties(
1158                        exp.Properties(
1159                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1160                        ),
1161                        wrapped=False,
1162                    )
1163                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1164                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1165
1166        postindex_props_sql = ""
1167        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1168            postindex_props_sql = self.properties(
1169                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1170                wrapped=False,
1171                prefix=" ",
1172            )
1173
1174        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1175        indexes = f" {indexes}" if indexes else ""
1176        index_sql = indexes + postindex_props_sql
1177
1178        replace = " OR REPLACE" if expression.args.get("replace") else ""
1179        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1180        unique = " UNIQUE" if expression.args.get("unique") else ""
1181
1182        clustered = expression.args.get("clustered")
1183        if clustered is None:
1184            clustered_sql = ""
1185        elif clustered:
1186            clustered_sql = " CLUSTERED COLUMNSTORE"
1187        else:
1188            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1189
1190        postcreate_props_sql = ""
1191        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1192            postcreate_props_sql = self.properties(
1193                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1194                sep=" ",
1195                prefix=" ",
1196                wrapped=False,
1197            )
1198
1199        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1200
1201        postexpression_props_sql = ""
1202        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1203            postexpression_props_sql = self.properties(
1204                exp.Properties(
1205                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1206                ),
1207                sep=" ",
1208                prefix=" ",
1209                wrapped=False,
1210            )
1211
1212        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1213        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1214        no_schema_binding = (
1215            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1216        )
1217
1218        clone = self.sql(expression, "clone")
1219        clone = f" {clone}" if clone else ""
1220
1221        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1222            properties_expression = f"{expression_sql}{properties_sql}"
1223        else:
1224            properties_expression = f"{properties_sql}{expression_sql}"
1225
1226        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1227        return self.prepend_ctes(expression, expression_sql)
def sequenceproperties_sql(self, expression: sqlglot.expressions.SequenceProperties) -> str:
1229    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1230        start = self.sql(expression, "start")
1231        start = f"START WITH {start}" if start else ""
1232        increment = self.sql(expression, "increment")
1233        increment = f" INCREMENT BY {increment}" if increment else ""
1234        minvalue = self.sql(expression, "minvalue")
1235        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1236        maxvalue = self.sql(expression, "maxvalue")
1237        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1238        owned = self.sql(expression, "owned")
1239        owned = f" OWNED BY {owned}" if owned else ""
1240
1241        cache = expression.args.get("cache")
1242        if cache is None:
1243            cache_str = ""
1244        elif cache is True:
1245            cache_str = " CACHE"
1246        else:
1247            cache_str = f" CACHE {cache}"
1248
1249        options = self.expressions(expression, key="options", flat=True, sep=" ")
1250        options = f" {options}" if options else ""
1251
1252        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
def clone_sql(self, expression: sqlglot.expressions.Clone) -> str:
1254    def clone_sql(self, expression: exp.Clone) -> str:
1255        this = self.sql(expression, "this")
1256        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1257        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1258        return f"{shallow}{keyword} {this}"
def describe_sql(self, expression: sqlglot.expressions.Describe) -> str:
1260    def describe_sql(self, expression: exp.Describe) -> str:
1261        style = expression.args.get("style")
1262        style = f" {style}" if style else ""
1263        partition = self.sql(expression, "partition")
1264        partition = f" {partition}" if partition else ""
1265        format = self.sql(expression, "format")
1266        format = f" {format}" if format else ""
1267
1268        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
def heredoc_sql(self, expression: sqlglot.expressions.Heredoc) -> str:
1270    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1271        tag = self.sql(expression, "tag")
1272        return f"${tag}${self.sql(expression, 'this')}${tag}$"
def prepend_ctes(self, expression: sqlglot.expressions.Expression, sql: str) -> str:
1274    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1275        with_ = self.sql(expression, "with")
1276        if with_:
1277            sql = f"{with_}{self.sep()}{sql}"
1278        return sql
def with_sql(self, expression: sqlglot.expressions.With) -> str:
1280    def with_sql(self, expression: exp.With) -> str:
1281        sql = self.expressions(expression, flat=True)
1282        recursive = (
1283            "RECURSIVE "
1284            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1285            else ""
1286        )
1287        search = self.sql(expression, "search")
1288        search = f" {search}" if search else ""
1289
1290        return f"WITH {recursive}{sql}{search}"
def cte_sql(self, expression: sqlglot.expressions.CTE) -> str:
1292    def cte_sql(self, expression: exp.CTE) -> str:
1293        alias = expression.args.get("alias")
1294        if alias:
1295            alias.add_comments(expression.pop_comments())
1296
1297        alias_sql = self.sql(expression, "alias")
1298
1299        materialized = expression.args.get("materialized")
1300        if materialized is False:
1301            materialized = "NOT MATERIALIZED "
1302        elif materialized:
1303            materialized = "MATERIALIZED "
1304
1305        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
def tablealias_sql(self, expression: sqlglot.expressions.TableAlias) -> str:
1307    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1308        alias = self.sql(expression, "this")
1309        columns = self.expressions(expression, key="columns", flat=True)
1310        columns = f"({columns})" if columns else ""
1311
1312        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1313            columns = ""
1314            self.unsupported("Named columns are not supported in table alias.")
1315
1316        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1317            alias = self._next_name()
1318
1319        return f"{alias}{columns}"
def bitstring_sql(self, expression: sqlglot.expressions.BitString) -> str:
1321    def bitstring_sql(self, expression: exp.BitString) -> str:
1322        this = self.sql(expression, "this")
1323        if self.dialect.BIT_START:
1324            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1325        return f"{int(this, 2)}"
def hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1327    def hexstring_sql(
1328        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1329    ) -> str:
1330        this = self.sql(expression, "this")
1331        is_integer_type = expression.args.get("is_integer")
1332
1333        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1334            not self.dialect.HEX_START and not binary_function_repr
1335        ):
1336            # Integer representation will be returned if:
1337            # - The read dialect treats the hex value as integer literal but not the write
1338            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1339            return f"{int(this, 16)}"
1340
1341        if not is_integer_type:
1342            # Read dialect treats the hex value as BINARY/BLOB
1343            if binary_function_repr:
1344                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1345                return self.func(binary_function_repr, exp.Literal.string(this))
1346            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1347                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1348                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1349
1350        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
def bytestring_sql(self, expression: sqlglot.expressions.ByteString) -> str:
1352    def bytestring_sql(self, expression: exp.ByteString) -> str:
1353        this = self.sql(expression, "this")
1354        if self.dialect.BYTE_START:
1355            return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1356        return this
def unicodestring_sql(self, expression: sqlglot.expressions.UnicodeString) -> str:
1358    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1359        this = self.sql(expression, "this")
1360        escape = expression.args.get("escape")
1361
1362        if self.dialect.UNICODE_START:
1363            escape_substitute = r"\\\1"
1364            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1365        else:
1366            escape_substitute = r"\\u\1"
1367            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1368
1369        if escape:
1370            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1371            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1372        else:
1373            escape_pattern = ESCAPED_UNICODE_RE
1374            escape_sql = ""
1375
1376        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1377            this = escape_pattern.sub(escape_substitute, this)
1378
1379        return f"{left_quote}{this}{right_quote}{escape_sql}"
def rawstring_sql(self, expression: sqlglot.expressions.RawString) -> str:
1381    def rawstring_sql(self, expression: exp.RawString) -> str:
1382        string = expression.this
1383        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1384            string = string.replace("\\", "\\\\")
1385
1386        string = self.escape_str(string, escape_backslash=False)
1387        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
def datatypeparam_sql(self, expression: sqlglot.expressions.DataTypeParam) -> str:
1389    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1390        this = self.sql(expression, "this")
1391        specifier = self.sql(expression, "expression")
1392        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1393        return f"{this}{specifier}"
def datatype_sql(self, expression: sqlglot.expressions.DataType) -> str:
1395    def datatype_sql(self, expression: exp.DataType) -> str:
1396        nested = ""
1397        values = ""
1398        interior = self.expressions(expression, flat=True)
1399
1400        type_value = expression.this
1401        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1402            type_sql = self.sql(expression, "kind")
1403        else:
1404            type_sql = (
1405                self.TYPE_MAPPING.get(type_value, type_value.value)
1406                if isinstance(type_value, exp.DataType.Type)
1407                else type_value
1408            )
1409
1410        if interior:
1411            if expression.args.get("nested"):
1412                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1413                if expression.args.get("values") is not None:
1414                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1415                    values = self.expressions(expression, key="values", flat=True)
1416                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1417            elif type_value == exp.DataType.Type.INTERVAL:
1418                nested = f" {interior}"
1419            else:
1420                nested = f"({interior})"
1421
1422        type_sql = f"{type_sql}{nested}{values}"
1423        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1424            exp.DataType.Type.TIMETZ,
1425            exp.DataType.Type.TIMESTAMPTZ,
1426        ):
1427            type_sql = f"{type_sql} WITH TIME ZONE"
1428
1429        return type_sql
def directory_sql(self, expression: sqlglot.expressions.Directory) -> str:
1431    def directory_sql(self, expression: exp.Directory) -> str:
1432        local = "LOCAL " if expression.args.get("local") else ""
1433        row_format = self.sql(expression, "row_format")
1434        row_format = f" {row_format}" if row_format else ""
1435        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
def delete_sql(self, expression: sqlglot.expressions.Delete) -> str:
1437    def delete_sql(self, expression: exp.Delete) -> str:
1438        this = self.sql(expression, "this")
1439        this = f" FROM {this}" if this else ""
1440        using = self.sql(expression, "using")
1441        using = f" USING {using}" if using else ""
1442        cluster = self.sql(expression, "cluster")
1443        cluster = f" {cluster}" if cluster else ""
1444        where = self.sql(expression, "where")
1445        returning = self.sql(expression, "returning")
1446        limit = self.sql(expression, "limit")
1447        tables = self.expressions(expression, key="tables")
1448        tables = f" {tables}" if tables else ""
1449        if self.RETURNING_END:
1450            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1451        else:
1452            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1453        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
def drop_sql(self, expression: sqlglot.expressions.Drop) -> str:
1455    def drop_sql(self, expression: exp.Drop) -> str:
1456        this = self.sql(expression, "this")
1457        expressions = self.expressions(expression, flat=True)
1458        expressions = f" ({expressions})" if expressions else ""
1459        kind = expression.args["kind"]
1460        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1461        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1462        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1463        on_cluster = self.sql(expression, "cluster")
1464        on_cluster = f" {on_cluster}" if on_cluster else ""
1465        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1466        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1467        cascade = " CASCADE" if expression.args.get("cascade") else ""
1468        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1469        purge = " PURGE" if expression.args.get("purge") else ""
1470        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
def set_operation(self, expression: sqlglot.expressions.SetOperation) -> str:
1472    def set_operation(self, expression: exp.SetOperation) -> str:
1473        op_type = type(expression)
1474        op_name = op_type.key.upper()
1475
1476        distinct = expression.args.get("distinct")
1477        if (
1478            distinct is False
1479            and op_type in (exp.Except, exp.Intersect)
1480            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1481        ):
1482            self.unsupported(f"{op_name} ALL is not supported")
1483
1484        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1485
1486        if distinct is None:
1487            distinct = default_distinct
1488            if distinct is None:
1489                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1490
1491        if distinct is default_distinct:
1492            distinct_or_all = ""
1493        else:
1494            distinct_or_all = " DISTINCT" if distinct else " ALL"
1495
1496        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1497        side_kind = f"{side_kind} " if side_kind else ""
1498
1499        by_name = " BY NAME" if expression.args.get("by_name") else ""
1500        on = self.expressions(expression, key="on", flat=True)
1501        on = f" ON ({on})" if on else ""
1502
1503        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
def set_operations(self, expression: sqlglot.expressions.SetOperation) -> str:
1505    def set_operations(self, expression: exp.SetOperation) -> str:
1506        if not self.SET_OP_MODIFIERS:
1507            limit = expression.args.get("limit")
1508            order = expression.args.get("order")
1509
1510            if limit or order:
1511                select = self._move_ctes_to_top_level(
1512                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1513                )
1514
1515                if limit:
1516                    select = select.limit(limit.pop(), copy=False)
1517                if order:
1518                    select = select.order_by(order.pop(), copy=False)
1519                return self.sql(select)
1520
1521        sqls: t.List[str] = []
1522        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1523
1524        while stack:
1525            node = stack.pop()
1526
1527            if isinstance(node, exp.SetOperation):
1528                stack.append(node.expression)
1529                stack.append(
1530                    self.maybe_comment(
1531                        self.set_operation(node), comments=node.comments, separated=True
1532                    )
1533                )
1534                stack.append(node.this)
1535            else:
1536                sqls.append(self.sql(node))
1537
1538        this = self.sep().join(sqls)
1539        this = self.query_modifiers(expression, this)
1540        return self.prepend_ctes(expression, this)
def fetch_sql(self, expression: sqlglot.expressions.Fetch) -> str:
1542    def fetch_sql(self, expression: exp.Fetch) -> str:
1543        direction = expression.args.get("direction")
1544        direction = f" {direction}" if direction else ""
1545        count = self.sql(expression, "count")
1546        count = f" {count}" if count else ""
1547        limit_options = self.sql(expression, "limit_options")
1548        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1549        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
def limitoptions_sql(self, expression: sqlglot.expressions.LimitOptions) -> str:
1551    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1552        percent = " PERCENT" if expression.args.get("percent") else ""
1553        rows = " ROWS" if expression.args.get("rows") else ""
1554        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1555        if not with_ties and rows:
1556            with_ties = " ONLY"
1557        return f"{percent}{rows}{with_ties}"
def filter_sql(self, expression: sqlglot.expressions.Filter) -> str:
1559    def filter_sql(self, expression: exp.Filter) -> str:
1560        if self.AGGREGATE_FILTER_SUPPORTED:
1561            this = self.sql(expression, "this")
1562            where = self.sql(expression, "expression").strip()
1563            return f"{this} FILTER({where})"
1564
1565        agg = expression.this
1566        agg_arg = agg.this
1567        cond = expression.expression.this
1568        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1569        return self.sql(agg)
def hint_sql(self, expression: sqlglot.expressions.Hint) -> str:
1571    def hint_sql(self, expression: exp.Hint) -> str:
1572        if not self.QUERY_HINTS:
1573            self.unsupported("Hints are not supported")
1574            return ""
1575
1576        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
def indexparameters_sql(self, expression: sqlglot.expressions.IndexParameters) -> str:
1578    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1579        using = self.sql(expression, "using")
1580        using = f" USING {using}" if using else ""
1581        columns = self.expressions(expression, key="columns", flat=True)
1582        columns = f"({columns})" if columns else ""
1583        partition_by = self.expressions(expression, key="partition_by", flat=True)
1584        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1585        where = self.sql(expression, "where")
1586        include = self.expressions(expression, key="include", flat=True)
1587        if include:
1588            include = f" INCLUDE ({include})"
1589        with_storage = self.expressions(expression, key="with_storage", flat=True)
1590        with_storage = f" WITH ({with_storage})" if with_storage else ""
1591        tablespace = self.sql(expression, "tablespace")
1592        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1593        on = self.sql(expression, "on")
1594        on = f" ON {on}" if on else ""
1595
1596        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
def index_sql(self, expression: sqlglot.expressions.Index) -> str:
1598    def index_sql(self, expression: exp.Index) -> str:
1599        unique = "UNIQUE " if expression.args.get("unique") else ""
1600        primary = "PRIMARY " if expression.args.get("primary") else ""
1601        amp = "AMP " if expression.args.get("amp") else ""
1602        name = self.sql(expression, "this")
1603        name = f"{name} " if name else ""
1604        table = self.sql(expression, "table")
1605        table = f"{self.INDEX_ON} {table}" if table else ""
1606
1607        index = "INDEX " if not table else ""
1608
1609        params = self.sql(expression, "params")
1610        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
def identifier_sql(self, expression: sqlglot.expressions.Identifier) -> str:
1612    def identifier_sql(self, expression: exp.Identifier) -> str:
1613        text = expression.name
1614        lower = text.lower()
1615        text = lower if self.normalize and not expression.quoted else text
1616        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1617        if (
1618            expression.quoted
1619            or self.dialect.can_identify(text, self.identify)
1620            or lower in self.RESERVED_KEYWORDS
1621            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1622        ):
1623            text = f"{self._identifier_start}{text}{self._identifier_end}"
1624        return text
def hex_sql(self, expression: sqlglot.expressions.Hex) -> str:
1626    def hex_sql(self, expression: exp.Hex) -> str:
1627        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1628        if self.dialect.HEX_LOWERCASE:
1629            text = self.func("LOWER", text)
1630
1631        return text
def lowerhex_sql(self, expression: sqlglot.expressions.LowerHex) -> str:
1633    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1634        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1635        if not self.dialect.HEX_LOWERCASE:
1636            text = self.func("LOWER", text)
1637        return text
def inputoutputformat_sql(self, expression: sqlglot.expressions.InputOutputFormat) -> str:
1639    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1640        input_format = self.sql(expression, "input_format")
1641        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1642        output_format = self.sql(expression, "output_format")
1643        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1644        return self.sep().join((input_format, output_format))
def national_sql(self, expression: sqlglot.expressions.National, prefix: str = 'N') -> str:
1646    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1647        string = self.sql(exp.Literal.string(expression.name))
1648        return f"{prefix}{string}"
def partition_sql(self, expression: sqlglot.expressions.Partition) -> str:
1650    def partition_sql(self, expression: exp.Partition) -> str:
1651        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1652        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
def properties_sql(self, expression: sqlglot.expressions.Properties) -> str:
1654    def properties_sql(self, expression: exp.Properties) -> str:
1655        root_properties = []
1656        with_properties = []
1657
1658        for p in expression.expressions:
1659            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1660            if p_loc == exp.Properties.Location.POST_WITH:
1661                with_properties.append(p)
1662            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1663                root_properties.append(p)
1664
1665        root_props = self.root_properties(exp.Properties(expressions=root_properties))
1666        with_props = self.with_properties(exp.Properties(expressions=with_properties))
1667
1668        if root_props and with_props and not self.pretty:
1669            with_props = " " + with_props
1670
1671        return root_props + with_props
def root_properties(self, properties: sqlglot.expressions.Properties) -> str:
1673    def root_properties(self, properties: exp.Properties) -> str:
1674        if properties.expressions:
1675            return self.expressions(properties, indent=False, sep=" ")
1676        return ""
def properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1678    def properties(
1679        self,
1680        properties: exp.Properties,
1681        prefix: str = "",
1682        sep: str = ", ",
1683        suffix: str = "",
1684        wrapped: bool = True,
1685    ) -> str:
1686        if properties.expressions:
1687            expressions = self.expressions(properties, sep=sep, indent=False)
1688            if expressions:
1689                expressions = self.wrap(expressions) if wrapped else expressions
1690                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1691        return ""
def with_properties(self, properties: sqlglot.expressions.Properties) -> str:
1693    def with_properties(self, properties: exp.Properties) -> str:
1694        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
def locate_properties(self, properties: sqlglot.expressions.Properties) -> DefaultDict:
1696    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1697        properties_locs = defaultdict(list)
1698        for p in properties.expressions:
1699            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1700            if p_loc != exp.Properties.Location.UNSUPPORTED:
1701                properties_locs[p_loc].append(p)
1702            else:
1703                self.unsupported(f"Unsupported property {p.key}")
1704
1705        return properties_locs
def property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1707    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1708        if isinstance(expression.this, exp.Dot):
1709            return self.sql(expression, "this")
1710        return f"'{expression.name}'" if string_key else expression.name
def property_sql(self, expression: sqlglot.expressions.Property) -> str:
1712    def property_sql(self, expression: exp.Property) -> str:
1713        property_cls = expression.__class__
1714        if property_cls == exp.Property:
1715            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1716
1717        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1718        if not property_name:
1719            self.unsupported(f"Unsupported property {expression.key}")
1720
1721        return f"{property_name}={self.sql(expression, 'this')}"
def likeproperty_sql(self, expression: sqlglot.expressions.LikeProperty) -> str:
1723    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1724        if self.SUPPORTS_CREATE_TABLE_LIKE:
1725            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1726            options = f" {options}" if options else ""
1727
1728            like = f"LIKE {self.sql(expression, 'this')}{options}"
1729            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1730                like = f"({like})"
1731
1732            return like
1733
1734        if expression.expressions:
1735            self.unsupported("Transpilation of LIKE property options is unsupported")
1736
1737        select = exp.select("*").from_(expression.this).limit(0)
1738        return f"AS {self.sql(select)}"
def fallbackproperty_sql(self, expression: sqlglot.expressions.FallbackProperty) -> str:
1740    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1741        no = "NO " if expression.args.get("no") else ""
1742        protection = " PROTECTION" if expression.args.get("protection") else ""
1743        return f"{no}FALLBACK{protection}"
def journalproperty_sql(self, expression: sqlglot.expressions.JournalProperty) -> str:
1745    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1746        no = "NO " if expression.args.get("no") else ""
1747        local = expression.args.get("local")
1748        local = f"{local} " if local else ""
1749        dual = "DUAL " if expression.args.get("dual") else ""
1750        before = "BEFORE " if expression.args.get("before") else ""
1751        after = "AFTER " if expression.args.get("after") else ""
1752        return f"{no}{local}{dual}{before}{after}JOURNAL"
def freespaceproperty_sql(self, expression: sqlglot.expressions.FreespaceProperty) -> str:
1754    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1755        freespace = self.sql(expression, "this")
1756        percent = " PERCENT" if expression.args.get("percent") else ""
1757        return f"FREESPACE={freespace}{percent}"
def checksumproperty_sql(self, expression: sqlglot.expressions.ChecksumProperty) -> str:
1759    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1760        if expression.args.get("default"):
1761            property = "DEFAULT"
1762        elif expression.args.get("on"):
1763            property = "ON"
1764        else:
1765            property = "OFF"
1766        return f"CHECKSUM={property}"
def mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1768    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1769        if expression.args.get("no"):
1770            return "NO MERGEBLOCKRATIO"
1771        if expression.args.get("default"):
1772            return "DEFAULT MERGEBLOCKRATIO"
1773
1774        percent = " PERCENT" if expression.args.get("percent") else ""
1775        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def datablocksizeproperty_sql(self, expression: sqlglot.expressions.DataBlocksizeProperty) -> str:
1777    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1778        default = expression.args.get("default")
1779        minimum = expression.args.get("minimum")
1780        maximum = expression.args.get("maximum")
1781        if default or minimum or maximum:
1782            if default:
1783                prop = "DEFAULT"
1784            elif minimum:
1785                prop = "MINIMUM"
1786            else:
1787                prop = "MAXIMUM"
1788            return f"{prop} DATABLOCKSIZE"
1789        units = expression.args.get("units")
1790        units = f" {units}" if units else ""
1791        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1793    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1794        autotemp = expression.args.get("autotemp")
1795        always = expression.args.get("always")
1796        default = expression.args.get("default")
1797        manual = expression.args.get("manual")
1798        never = expression.args.get("never")
1799
1800        if autotemp is not None:
1801            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1802        elif always:
1803            prop = "ALWAYS"
1804        elif default:
1805            prop = "DEFAULT"
1806        elif manual:
1807            prop = "MANUAL"
1808        elif never:
1809            prop = "NEVER"
1810        return f"BLOCKCOMPRESSION={prop}"
def isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1812    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1813        no = expression.args.get("no")
1814        no = " NO" if no else ""
1815        concurrent = expression.args.get("concurrent")
1816        concurrent = " CONCURRENT" if concurrent else ""
1817        target = self.sql(expression, "target")
1818        target = f" {target}" if target else ""
1819        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def partitionboundspec_sql(self, expression: sqlglot.expressions.PartitionBoundSpec) -> str:
1821    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1822        if isinstance(expression.this, list):
1823            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1824        if expression.this:
1825            modulus = self.sql(expression, "this")
1826            remainder = self.sql(expression, "expression")
1827            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1828
1829        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1830        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1831        return f"FROM ({from_expressions}) TO ({to_expressions})"
def partitionedofproperty_sql(self, expression: sqlglot.expressions.PartitionedOfProperty) -> str:
1833    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1834        this = self.sql(expression, "this")
1835
1836        for_values_or_default = expression.expression
1837        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1838            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1839        else:
1840            for_values_or_default = " DEFAULT"
1841
1842        return f"PARTITION OF {this}{for_values_or_default}"
def lockingproperty_sql(self, expression: sqlglot.expressions.LockingProperty) -> str:
1844    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1845        kind = expression.args.get("kind")
1846        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1847        for_or_in = expression.args.get("for_or_in")
1848        for_or_in = f" {for_or_in}" if for_or_in else ""
1849        lock_type = expression.args.get("lock_type")
1850        override = " OVERRIDE" if expression.args.get("override") else ""
1851        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
def withdataproperty_sql(self, expression: sqlglot.expressions.WithDataProperty) -> str:
1853    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1854        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1855        statistics = expression.args.get("statistics")
1856        statistics_sql = ""
1857        if statistics is not None:
1858            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1859        return f"{data_sql}{statistics_sql}"
def withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1861    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1862        this = self.sql(expression, "this")
1863        this = f"HISTORY_TABLE={this}" if this else ""
1864        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1865        data_consistency = (
1866            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1867        )
1868        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1869        retention_period = (
1870            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1871        )
1872
1873        if this:
1874            on_sql = self.func("ON", this, data_consistency, retention_period)
1875        else:
1876            on_sql = "ON" if expression.args.get("on") else "OFF"
1877
1878        sql = f"SYSTEM_VERSIONING={on_sql}"
1879
1880        return f"WITH({sql})" if expression.args.get("with") else sql
def insert_sql(self, expression: sqlglot.expressions.Insert) -> str:
1882    def insert_sql(self, expression: exp.Insert) -> str:
1883        hint = self.sql(expression, "hint")
1884        overwrite = expression.args.get("overwrite")
1885
1886        if isinstance(expression.this, exp.Directory):
1887            this = " OVERWRITE" if overwrite else " INTO"
1888        else:
1889            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1890
1891        stored = self.sql(expression, "stored")
1892        stored = f" {stored}" if stored else ""
1893        alternative = expression.args.get("alternative")
1894        alternative = f" OR {alternative}" if alternative else ""
1895        ignore = " IGNORE" if expression.args.get("ignore") else ""
1896        is_function = expression.args.get("is_function")
1897        if is_function:
1898            this = f"{this} FUNCTION"
1899        this = f"{this} {self.sql(expression, 'this')}"
1900
1901        exists = " IF EXISTS" if expression.args.get("exists") else ""
1902        where = self.sql(expression, "where")
1903        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1904        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1905        on_conflict = self.sql(expression, "conflict")
1906        on_conflict = f" {on_conflict}" if on_conflict else ""
1907        by_name = " BY NAME" if expression.args.get("by_name") else ""
1908        returning = self.sql(expression, "returning")
1909
1910        if self.RETURNING_END:
1911            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1912        else:
1913            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1914
1915        partition_by = self.sql(expression, "partition")
1916        partition_by = f" {partition_by}" if partition_by else ""
1917        settings = self.sql(expression, "settings")
1918        settings = f" {settings}" if settings else ""
1919
1920        source = self.sql(expression, "source")
1921        source = f"TABLE {source}" if source else ""
1922
1923        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1924        return self.prepend_ctes(expression, sql)
def introducer_sql(self, expression: sqlglot.expressions.Introducer) -> str:
1926    def introducer_sql(self, expression: exp.Introducer) -> str:
1927        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def kill_sql(self, expression: sqlglot.expressions.Kill) -> str:
1929    def kill_sql(self, expression: exp.Kill) -> str:
1930        kind = self.sql(expression, "kind")
1931        kind = f" {kind}" if kind else ""
1932        this = self.sql(expression, "this")
1933        this = f" {this}" if this else ""
1934        return f"KILL{kind}{this}"
def pseudotype_sql(self, expression: sqlglot.expressions.PseudoType) -> str:
1936    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1937        return expression.name
def objectidentifier_sql(self, expression: sqlglot.expressions.ObjectIdentifier) -> str:
1939    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1940        return expression.name
def onconflict_sql(self, expression: sqlglot.expressions.OnConflict) -> str:
1942    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1943        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1944
1945        constraint = self.sql(expression, "constraint")
1946        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1947
1948        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1949        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1950        action = self.sql(expression, "action")
1951
1952        expressions = self.expressions(expression, flat=True)
1953        if expressions:
1954            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1955            expressions = f" {set_keyword}{expressions}"
1956
1957        where = self.sql(expression, "where")
1958        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def returning_sql(self, expression: sqlglot.expressions.Returning) -> str:
1960    def returning_sql(self, expression: exp.Returning) -> str:
1961        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
def rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1963    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1964        fields = self.sql(expression, "fields")
1965        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1966        escaped = self.sql(expression, "escaped")
1967        escaped = f" ESCAPED BY {escaped}" if escaped else ""
1968        items = self.sql(expression, "collection_items")
1969        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1970        keys = self.sql(expression, "map_keys")
1971        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1972        lines = self.sql(expression, "lines")
1973        lines = f" LINES TERMINATED BY {lines}" if lines else ""
1974        null = self.sql(expression, "null")
1975        null = f" NULL DEFINED AS {null}" if null else ""
1976        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
def withtablehint_sql(self, expression: sqlglot.expressions.WithTableHint) -> str:
1978    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1979        return f"WITH ({self.expressions(expression, flat=True)})"
def indextablehint_sql(self, expression: sqlglot.expressions.IndexTableHint) -> str:
1981    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1982        this = f"{self.sql(expression, 'this')} INDEX"
1983        target = self.sql(expression, "target")
1984        target = f" FOR {target}" if target else ""
1985        return f"{this}{target} ({self.expressions(expression, flat=True)})"
def historicaldata_sql(self, expression: sqlglot.expressions.HistoricalData) -> str:
1987    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
1988        this = self.sql(expression, "this")
1989        kind = self.sql(expression, "kind")
1990        expr = self.sql(expression, "expression")
1991        return f"{this} ({kind} => {expr})"
def table_parts(self, expression: sqlglot.expressions.Table) -> str:
1993    def table_parts(self, expression: exp.Table) -> str:
1994        return ".".join(
1995            self.sql(part)
1996            for part in (
1997                expression.args.get("catalog"),
1998                expression.args.get("db"),
1999                expression.args.get("this"),
2000            )
2001            if part is not None
2002        )
def table_sql(self, expression: sqlglot.expressions.Table, sep: str = ' AS ') -> str:
2004    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2005        table = self.table_parts(expression)
2006        only = "ONLY " if expression.args.get("only") else ""
2007        partition = self.sql(expression, "partition")
2008        partition = f" {partition}" if partition else ""
2009        version = self.sql(expression, "version")
2010        version = f" {version}" if version else ""
2011        alias = self.sql(expression, "alias")
2012        alias = f"{sep}{alias}" if alias else ""
2013
2014        sample = self.sql(expression, "sample")
2015        if self.dialect.ALIAS_POST_TABLESAMPLE:
2016            sample_pre_alias = sample
2017            sample_post_alias = ""
2018        else:
2019            sample_pre_alias = ""
2020            sample_post_alias = sample
2021
2022        hints = self.expressions(expression, key="hints", sep=" ")
2023        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2024        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2025        joins = self.indent(
2026            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2027        )
2028        laterals = self.expressions(expression, key="laterals", sep="")
2029
2030        file_format = self.sql(expression, "format")
2031        if file_format:
2032            pattern = self.sql(expression, "pattern")
2033            pattern = f", PATTERN => {pattern}" if pattern else ""
2034            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2035
2036        ordinality = expression.args.get("ordinality") or ""
2037        if ordinality:
2038            ordinality = f" WITH ORDINALITY{alias}"
2039            alias = ""
2040
2041        when = self.sql(expression, "when")
2042        if when:
2043            table = f"{table} {when}"
2044
2045        changes = self.sql(expression, "changes")
2046        changes = f" {changes}" if changes else ""
2047
2048        rows_from = self.expressions(expression, key="rows_from")
2049        if rows_from:
2050            table = f"ROWS FROM {self.wrap(rows_from)}"
2051
2052        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
def tablefromrows_sql(self, expression: sqlglot.expressions.TableFromRows) -> str:
2054    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2055        table = self.func("TABLE", expression.this)
2056        alias = self.sql(expression, "alias")
2057        alias = f" AS {alias}" if alias else ""
2058        sample = self.sql(expression, "sample")
2059        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2060        joins = self.indent(
2061            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2062        )
2063        return f"{table}{alias}{pivots}{sample}{joins}"
def tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2065    def tablesample_sql(
2066        self,
2067        expression: exp.TableSample,
2068        tablesample_keyword: t.Optional[str] = None,
2069    ) -> str:
2070        method = self.sql(expression, "method")
2071        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2072        numerator = self.sql(expression, "bucket_numerator")
2073        denominator = self.sql(expression, "bucket_denominator")
2074        field = self.sql(expression, "bucket_field")
2075        field = f" ON {field}" if field else ""
2076        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2077        seed = self.sql(expression, "seed")
2078        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2079
2080        size = self.sql(expression, "size")
2081        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2082            size = f"{size} ROWS"
2083
2084        percent = self.sql(expression, "percent")
2085        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2086            percent = f"{percent} PERCENT"
2087
2088        expr = f"{bucket}{percent}{size}"
2089        if self.TABLESAMPLE_REQUIRES_PARENS:
2090            expr = f"({expr})"
2091
2092        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
def pivot_sql(self, expression: sqlglot.expressions.Pivot) -> str:
2094    def pivot_sql(self, expression: exp.Pivot) -> str:
2095        expressions = self.expressions(expression, flat=True)
2096        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2097
2098        group = self.sql(expression, "group")
2099
2100        if expression.this:
2101            this = self.sql(expression, "this")
2102            if not expressions:
2103                return f"UNPIVOT {this}"
2104
2105            on = f"{self.seg('ON')} {expressions}"
2106            into = self.sql(expression, "into")
2107            into = f"{self.seg('INTO')} {into}" if into else ""
2108            using = self.expressions(expression, key="using", flat=True)
2109            using = f"{self.seg('USING')} {using}" if using else ""
2110            return f"{direction} {this}{on}{into}{using}{group}"
2111
2112        alias = self.sql(expression, "alias")
2113        alias = f" AS {alias}" if alias else ""
2114
2115        fields = self.expressions(
2116            expression,
2117            "fields",
2118            sep=" ",
2119            dynamic=True,
2120            new_line=True,
2121            skip_first=True,
2122            skip_last=True,
2123        )
2124
2125        include_nulls = expression.args.get("include_nulls")
2126        if include_nulls is not None:
2127            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2128        else:
2129            nulls = ""
2130
2131        default_on_null = self.sql(expression, "default_on_null")
2132        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2133        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
def version_sql(self, expression: sqlglot.expressions.Version) -> str:
2135    def version_sql(self, expression: exp.Version) -> str:
2136        this = f"FOR {expression.name}"
2137        kind = expression.text("kind")
2138        expr = self.sql(expression, "expression")
2139        return f"{this} {kind} {expr}"
def tuple_sql(self, expression: sqlglot.expressions.Tuple) -> str:
2141    def tuple_sql(self, expression: exp.Tuple) -> str:
2142        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
def update_sql(self, expression: sqlglot.expressions.Update) -> str:
2144    def update_sql(self, expression: exp.Update) -> str:
2145        this = self.sql(expression, "this")
2146        set_sql = self.expressions(expression, flat=True)
2147        from_sql = self.sql(expression, "from")
2148        where_sql = self.sql(expression, "where")
2149        returning = self.sql(expression, "returning")
2150        order = self.sql(expression, "order")
2151        limit = self.sql(expression, "limit")
2152        if self.RETURNING_END:
2153            expression_sql = f"{from_sql}{where_sql}{returning}"
2154        else:
2155            expression_sql = f"{returning}{from_sql}{where_sql}"
2156        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2157        return self.prepend_ctes(expression, sql)
def values_sql( self, expression: sqlglot.expressions.Values, values_as_table: bool = True) -> str:
2159    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2160        values_as_table = values_as_table and self.VALUES_AS_TABLE
2161
2162        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2163        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2164            args = self.expressions(expression)
2165            alias = self.sql(expression, "alias")
2166            values = f"VALUES{self.seg('')}{args}"
2167            values = (
2168                f"({values})"
2169                if self.WRAP_DERIVED_VALUES
2170                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2171                else values
2172            )
2173            return f"{values} AS {alias}" if alias else values
2174
2175        # Converts `VALUES...` expression into a series of select unions.
2176        alias_node = expression.args.get("alias")
2177        column_names = alias_node and alias_node.columns
2178
2179        selects: t.List[exp.Query] = []
2180
2181        for i, tup in enumerate(expression.expressions):
2182            row = tup.expressions
2183
2184            if i == 0 and column_names:
2185                row = [
2186                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2187                ]
2188
2189            selects.append(exp.Select(expressions=row))
2190
2191        if self.pretty:
2192            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2193            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2194            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2195            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2196            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2197
2198        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2199        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2200        return f"({unions}){alias}"
def var_sql(self, expression: sqlglot.expressions.Var) -> str:
2202    def var_sql(self, expression: exp.Var) -> str:
2203        return self.sql(expression, "this")
@unsupported_args('expressions')
def into_sql(self, expression: sqlglot.expressions.Into) -> str:
2205    @unsupported_args("expressions")
2206    def into_sql(self, expression: exp.Into) -> str:
2207        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2208        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2209        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
def from_sql(self, expression: sqlglot.expressions.From) -> str:
2211    def from_sql(self, expression: exp.From) -> str:
2212        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
def groupingsets_sql(self, expression: sqlglot.expressions.GroupingSets) -> str:
2214    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2215        grouping_sets = self.expressions(expression, indent=False)
2216        return f"GROUPING SETS {self.wrap(grouping_sets)}"
def rollup_sql(self, expression: sqlglot.expressions.Rollup) -> str:
2218    def rollup_sql(self, expression: exp.Rollup) -> str:
2219        expressions = self.expressions(expression, indent=False)
2220        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
def cube_sql(self, expression: sqlglot.expressions.Cube) -> str:
2222    def cube_sql(self, expression: exp.Cube) -> str:
2223        expressions = self.expressions(expression, indent=False)
2224        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
def group_sql(self, expression: sqlglot.expressions.Group) -> str:
2226    def group_sql(self, expression: exp.Group) -> str:
2227        group_by_all = expression.args.get("all")
2228        if group_by_all is True:
2229            modifier = " ALL"
2230        elif group_by_all is False:
2231            modifier = " DISTINCT"
2232        else:
2233            modifier = ""
2234
2235        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2236
2237        grouping_sets = self.expressions(expression, key="grouping_sets")
2238        cube = self.expressions(expression, key="cube")
2239        rollup = self.expressions(expression, key="rollup")
2240
2241        groupings = csv(
2242            self.seg(grouping_sets) if grouping_sets else "",
2243            self.seg(cube) if cube else "",
2244            self.seg(rollup) if rollup else "",
2245            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2246            sep=self.GROUPINGS_SEP,
2247        )
2248
2249        if (
2250            expression.expressions
2251            and groupings
2252            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2253        ):
2254            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2255
2256        return f"{group_by}{groupings}"
def having_sql(self, expression: sqlglot.expressions.Having) -> str:
2258    def having_sql(self, expression: exp.Having) -> str:
2259        this = self.indent(self.sql(expression, "this"))
2260        return f"{self.seg('HAVING')}{self.sep()}{this}"
def connect_sql(self, expression: sqlglot.expressions.Connect) -> str:
2262    def connect_sql(self, expression: exp.Connect) -> str:
2263        start = self.sql(expression, "start")
2264        start = self.seg(f"START WITH {start}") if start else ""
2265        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2266        connect = self.sql(expression, "connect")
2267        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2268        return start + connect
def prior_sql(self, expression: sqlglot.expressions.Prior) -> str:
2270    def prior_sql(self, expression: exp.Prior) -> str:
2271        return f"PRIOR {self.sql(expression, 'this')}"
def join_sql(self, expression: sqlglot.expressions.Join) -> str:
2273    def join_sql(self, expression: exp.Join) -> str:
2274        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2275            side = None
2276        else:
2277            side = expression.side
2278
2279        op_sql = " ".join(
2280            op
2281            for op in (
2282                expression.method,
2283                "GLOBAL" if expression.args.get("global") else None,
2284                side,
2285                expression.kind,
2286                expression.hint if self.JOIN_HINTS else None,
2287            )
2288            if op
2289        )
2290        match_cond = self.sql(expression, "match_condition")
2291        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2292        on_sql = self.sql(expression, "on")
2293        using = expression.args.get("using")
2294
2295        if not on_sql and using:
2296            on_sql = csv(*(self.sql(column) for column in using))
2297
2298        this = expression.this
2299        this_sql = self.sql(this)
2300
2301        exprs = self.expressions(expression)
2302        if exprs:
2303            this_sql = f"{this_sql},{self.seg(exprs)}"
2304
2305        if on_sql:
2306            on_sql = self.indent(on_sql, skip_first=True)
2307            space = self.seg(" " * self.pad) if self.pretty else " "
2308            if using:
2309                on_sql = f"{space}USING ({on_sql})"
2310            else:
2311                on_sql = f"{space}ON {on_sql}"
2312        elif not op_sql:
2313            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2314                return f" {this_sql}"
2315
2316            return f", {this_sql}"
2317
2318        if op_sql != "STRAIGHT_JOIN":
2319            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2320
2321        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2322        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def lambda_sql( self, expression: sqlglot.expressions.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2324    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2325        args = self.expressions(expression, flat=True)
2326        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2327        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
def lateral_op(self, expression: sqlglot.expressions.Lateral) -> str:
2329    def lateral_op(self, expression: exp.Lateral) -> str:
2330        cross_apply = expression.args.get("cross_apply")
2331
2332        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2333        if cross_apply is True:
2334            op = "INNER JOIN "
2335        elif cross_apply is False:
2336            op = "LEFT JOIN "
2337        else:
2338            op = ""
2339
2340        return f"{op}LATERAL"
def lateral_sql(self, expression: sqlglot.expressions.Lateral) -> str:
2342    def lateral_sql(self, expression: exp.Lateral) -> str:
2343        this = self.sql(expression, "this")
2344
2345        if expression.args.get("view"):
2346            alias = expression.args["alias"]
2347            columns = self.expressions(alias, key="columns", flat=True)
2348            table = f" {alias.name}" if alias.name else ""
2349            columns = f" AS {columns}" if columns else ""
2350            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2351            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2352
2353        alias = self.sql(expression, "alias")
2354        alias = f" AS {alias}" if alias else ""
2355
2356        ordinality = expression.args.get("ordinality") or ""
2357        if ordinality:
2358            ordinality = f" WITH ORDINALITY{alias}"
2359            alias = ""
2360
2361        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
def limit_sql(self, expression: sqlglot.expressions.Limit, top: bool = False) -> str:
2363    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2364        this = self.sql(expression, "this")
2365
2366        args = [
2367            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2368            for e in (expression.args.get(k) for k in ("offset", "expression"))
2369            if e
2370        ]
2371
2372        args_sql = ", ".join(self.sql(e) for e in args)
2373        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2374        expressions = self.expressions(expression, flat=True)
2375        limit_options = self.sql(expression, "limit_options")
2376        expressions = f" BY {expressions}" if expressions else ""
2377
2378        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
def offset_sql(self, expression: sqlglot.expressions.Offset) -> str:
2380    def offset_sql(self, expression: exp.Offset) -> str:
2381        this = self.sql(expression, "this")
2382        value = expression.expression
2383        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2384        expressions = self.expressions(expression, flat=True)
2385        expressions = f" BY {expressions}" if expressions else ""
2386        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
def setitem_sql(self, expression: sqlglot.expressions.SetItem) -> str:
2388    def setitem_sql(self, expression: exp.SetItem) -> str:
2389        kind = self.sql(expression, "kind")
2390        kind = f"{kind} " if kind else ""
2391        this = self.sql(expression, "this")
2392        expressions = self.expressions(expression)
2393        collate = self.sql(expression, "collate")
2394        collate = f" COLLATE {collate}" if collate else ""
2395        global_ = "GLOBAL " if expression.args.get("global") else ""
2396        return f"{global_}{kind}{this}{expressions}{collate}"
def set_sql(self, expression: sqlglot.expressions.Set) -> str:
2398    def set_sql(self, expression: exp.Set) -> str:
2399        expressions = f" {self.expressions(expression, flat=True)}"
2400        tag = " TAG" if expression.args.get("tag") else ""
2401        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
def pragma_sql(self, expression: sqlglot.expressions.Pragma) -> str:
2403    def pragma_sql(self, expression: exp.Pragma) -> str:
2404        return f"PRAGMA {self.sql(expression, 'this')}"
def lock_sql(self, expression: sqlglot.expressions.Lock) -> str:
2406    def lock_sql(self, expression: exp.Lock) -> str:
2407        if not self.LOCKING_READS_SUPPORTED:
2408            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2409            return ""
2410
2411        update = expression.args["update"]
2412        key = expression.args.get("key")
2413        if update:
2414            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2415        else:
2416            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2417        expressions = self.expressions(expression, flat=True)
2418        expressions = f" OF {expressions}" if expressions else ""
2419        wait = expression.args.get("wait")
2420
2421        if wait is not None:
2422            if isinstance(wait, exp.Literal):
2423                wait = f" WAIT {self.sql(wait)}"
2424            else:
2425                wait = " NOWAIT" if wait else " SKIP LOCKED"
2426
2427        return f"{lock_type}{expressions}{wait or ''}"
def literal_sql(self, expression: sqlglot.expressions.Literal) -> str:
2429    def literal_sql(self, expression: exp.Literal) -> str:
2430        text = expression.this or ""
2431        if expression.is_string:
2432            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2433        return text
def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2435    def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2436        if self.dialect.ESCAPED_SEQUENCES:
2437            to_escaped = self.dialect.ESCAPED_SEQUENCES
2438            text = "".join(
2439                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2440            )
2441
2442        return self._replace_line_breaks(text).replace(
2443            self.dialect.QUOTE_END, self._escaped_quote_end
2444        )
def loaddata_sql(self, expression: sqlglot.expressions.LoadData) -> str:
2446    def loaddata_sql(self, expression: exp.LoadData) -> str:
2447        local = " LOCAL" if expression.args.get("local") else ""
2448        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2449        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2450        this = f" INTO TABLE {self.sql(expression, 'this')}"
2451        partition = self.sql(expression, "partition")
2452        partition = f" {partition}" if partition else ""
2453        input_format = self.sql(expression, "input_format")
2454        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2455        serde = self.sql(expression, "serde")
2456        serde = f" SERDE {serde}" if serde else ""
2457        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
def null_sql(self, *_) -> str:
2459    def null_sql(self, *_) -> str:
2460        return "NULL"
def boolean_sql(self, expression: sqlglot.expressions.Boolean) -> str:
2462    def boolean_sql(self, expression: exp.Boolean) -> str:
2463        return "TRUE" if expression.this else "FALSE"
def order_sql(self, expression: sqlglot.expressions.Order, flat: bool = False) -> str:
2465    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2466        this = self.sql(expression, "this")
2467        this = f"{this} " if this else this
2468        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2469        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
def withfill_sql(self, expression: sqlglot.expressions.WithFill) -> str:
2471    def withfill_sql(self, expression: exp.WithFill) -> str:
2472        from_sql = self.sql(expression, "from")
2473        from_sql = f" FROM {from_sql}" if from_sql else ""
2474        to_sql = self.sql(expression, "to")
2475        to_sql = f" TO {to_sql}" if to_sql else ""
2476        step_sql = self.sql(expression, "step")
2477        step_sql = f" STEP {step_sql}" if step_sql else ""
2478        interpolated_values = [
2479            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2480            if isinstance(e, exp.Alias)
2481            else self.sql(e, "this")
2482            for e in expression.args.get("interpolate") or []
2483        ]
2484        interpolate = (
2485            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2486        )
2487        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
def cluster_sql(self, expression: sqlglot.expressions.Cluster) -> str:
2489    def cluster_sql(self, expression: exp.Cluster) -> str:
2490        return self.op_expressions("CLUSTER BY", expression)
def distribute_sql(self, expression: sqlglot.expressions.Distribute) -> str:
2492    def distribute_sql(self, expression: exp.Distribute) -> str:
2493        return self.op_expressions("DISTRIBUTE BY", expression)
def sort_sql(self, expression: sqlglot.expressions.Sort) -> str:
2495    def sort_sql(self, expression: exp.Sort) -> str:
2496        return self.op_expressions("SORT BY", expression)
def ordered_sql(self, expression: sqlglot.expressions.Ordered) -> str:
2498    def ordered_sql(self, expression: exp.Ordered) -> str:
2499        desc = expression.args.get("desc")
2500        asc = not desc
2501
2502        nulls_first = expression.args.get("nulls_first")
2503        nulls_last = not nulls_first
2504        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2505        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2506        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2507
2508        this = self.sql(expression, "this")
2509
2510        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2511        nulls_sort_change = ""
2512        if nulls_first and (
2513            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2514        ):
2515            nulls_sort_change = " NULLS FIRST"
2516        elif (
2517            nulls_last
2518            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2519            and not nulls_are_last
2520        ):
2521            nulls_sort_change = " NULLS LAST"
2522
2523        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2524        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2525            window = expression.find_ancestor(exp.Window, exp.Select)
2526            if isinstance(window, exp.Window) and window.args.get("spec"):
2527                self.unsupported(
2528                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2529                )
2530                nulls_sort_change = ""
2531            elif self.NULL_ORDERING_SUPPORTED is False and (
2532                (asc and nulls_sort_change == " NULLS LAST")
2533                or (desc and nulls_sort_change == " NULLS FIRST")
2534            ):
2535                # BigQuery does not allow these ordering/nulls combinations when used under
2536                # an aggregation func or under a window containing one
2537                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2538
2539                if isinstance(ancestor, exp.Window):
2540                    ancestor = ancestor.this
2541                if isinstance(ancestor, exp.AggFunc):
2542                    self.unsupported(
2543                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2544                    )
2545                    nulls_sort_change = ""
2546            elif self.NULL_ORDERING_SUPPORTED is None:
2547                if expression.this.is_int:
2548                    self.unsupported(
2549                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2550                    )
2551                elif not isinstance(expression.this, exp.Rand):
2552                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2553                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2554                nulls_sort_change = ""
2555
2556        with_fill = self.sql(expression, "with_fill")
2557        with_fill = f" {with_fill}" if with_fill else ""
2558
2559        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def matchrecognizemeasure_sql(self, expression: sqlglot.expressions.MatchRecognizeMeasure) -> str:
2561    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2562        window_frame = self.sql(expression, "window_frame")
2563        window_frame = f"{window_frame} " if window_frame else ""
2564
2565        this = self.sql(expression, "this")
2566
2567        return f"{window_frame}{this}"
def matchrecognize_sql(self, expression: sqlglot.expressions.MatchRecognize) -> str:
2569    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2570        partition = self.partition_by_sql(expression)
2571        order = self.sql(expression, "order")
2572        measures = self.expressions(expression, key="measures")
2573        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2574        rows = self.sql(expression, "rows")
2575        rows = self.seg(rows) if rows else ""
2576        after = self.sql(expression, "after")
2577        after = self.seg(after) if after else ""
2578        pattern = self.sql(expression, "pattern")
2579        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2580        definition_sqls = [
2581            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2582            for definition in expression.args.get("define", [])
2583        ]
2584        definitions = self.expressions(sqls=definition_sqls)
2585        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2586        body = "".join(
2587            (
2588                partition,
2589                order,
2590                measures,
2591                rows,
2592                after,
2593                pattern,
2594                define,
2595            )
2596        )
2597        alias = self.sql(expression, "alias")
2598        alias = f" {alias}" if alias else ""
2599        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
def query_modifiers(self, expression: sqlglot.expressions.Expression, *sqls: str) -> str:
2601    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2602        limit = expression.args.get("limit")
2603
2604        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2605            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2606        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2607            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2608
2609        return csv(
2610            *sqls,
2611            *[self.sql(join) for join in expression.args.get("joins") or []],
2612            self.sql(expression, "match"),
2613            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2614            self.sql(expression, "prewhere"),
2615            self.sql(expression, "where"),
2616            self.sql(expression, "connect"),
2617            self.sql(expression, "group"),
2618            self.sql(expression, "having"),
2619            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2620            self.sql(expression, "order"),
2621            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2622            *self.after_limit_modifiers(expression),
2623            self.options_modifier(expression),
2624            self.for_modifiers(expression),
2625            sep="",
2626        )
def options_modifier(self, expression: sqlglot.expressions.Expression) -> str:
2628    def options_modifier(self, expression: exp.Expression) -> str:
2629        options = self.expressions(expression, key="options")
2630        return f" {options}" if options else ""
def for_modifiers(self, expression: sqlglot.expressions.Expression) -> str:
2632    def for_modifiers(self, expression: exp.Expression) -> str:
2633        for_modifiers = self.expressions(expression, key="for")
2634        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
def queryoption_sql(self, expression: sqlglot.expressions.QueryOption) -> str:
2636    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2637        self.unsupported("Unsupported query option.")
2638        return ""
def offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2640    def offset_limit_modifiers(
2641        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2642    ) -> t.List[str]:
2643        return [
2644            self.sql(expression, "offset") if fetch else self.sql(limit),
2645            self.sql(limit) if fetch else self.sql(expression, "offset"),
2646        ]
def after_limit_modifiers(self, expression: sqlglot.expressions.Expression) -> List[str]:
2648    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2649        locks = self.expressions(expression, key="locks", sep=" ")
2650        locks = f" {locks}" if locks else ""
2651        return [locks, self.sql(expression, "sample")]
def select_sql(self, expression: sqlglot.expressions.Select) -> str:
2653    def select_sql(self, expression: exp.Select) -> str:
2654        into = expression.args.get("into")
2655        if not self.SUPPORTS_SELECT_INTO and into:
2656            into.pop()
2657
2658        hint = self.sql(expression, "hint")
2659        distinct = self.sql(expression, "distinct")
2660        distinct = f" {distinct}" if distinct else ""
2661        kind = self.sql(expression, "kind")
2662
2663        limit = expression.args.get("limit")
2664        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2665            top = self.limit_sql(limit, top=True)
2666            limit.pop()
2667        else:
2668            top = ""
2669
2670        expressions = self.expressions(expression)
2671
2672        if kind:
2673            if kind in self.SELECT_KINDS:
2674                kind = f" AS {kind}"
2675            else:
2676                if kind == "STRUCT":
2677                    expressions = self.expressions(
2678                        sqls=[
2679                            self.sql(
2680                                exp.Struct(
2681                                    expressions=[
2682                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2683                                        if isinstance(e, exp.Alias)
2684                                        else e
2685                                        for e in expression.expressions
2686                                    ]
2687                                )
2688                            )
2689                        ]
2690                    )
2691                kind = ""
2692
2693        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2694        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2695
2696        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2697        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2698        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2699        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2700        sql = self.query_modifiers(
2701            expression,
2702            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2703            self.sql(expression, "into", comment=False),
2704            self.sql(expression, "from", comment=False),
2705        )
2706
2707        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2708        if expression.args.get("with"):
2709            sql = self.maybe_comment(sql, expression)
2710            expression.pop_comments()
2711
2712        sql = self.prepend_ctes(expression, sql)
2713
2714        if not self.SUPPORTS_SELECT_INTO and into:
2715            if into.args.get("temporary"):
2716                table_kind = " TEMPORARY"
2717            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2718                table_kind = " UNLOGGED"
2719            else:
2720                table_kind = ""
2721            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2722
2723        return sql
def schema_sql(self, expression: sqlglot.expressions.Schema) -> str:
2725    def schema_sql(self, expression: exp.Schema) -> str:
2726        this = self.sql(expression, "this")
2727        sql = self.schema_columns_sql(expression)
2728        return f"{this} {sql}" if this and sql else this or sql
def schema_columns_sql(self, expression: sqlglot.expressions.Schema) -> str:
2730    def schema_columns_sql(self, expression: exp.Schema) -> str:
2731        if expression.expressions:
2732            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2733        return ""
def star_sql(self, expression: sqlglot.expressions.Star) -> str:
2735    def star_sql(self, expression: exp.Star) -> str:
2736        except_ = self.expressions(expression, key="except", flat=True)
2737        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2738        replace = self.expressions(expression, key="replace", flat=True)
2739        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2740        rename = self.expressions(expression, key="rename", flat=True)
2741        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2742        return f"*{except_}{replace}{rename}"
def parameter_sql(self, expression: sqlglot.expressions.Parameter) -> str:
2744    def parameter_sql(self, expression: exp.Parameter) -> str:
2745        this = self.sql(expression, "this")
2746        return f"{self.PARAMETER_TOKEN}{this}"
def sessionparameter_sql(self, expression: sqlglot.expressions.SessionParameter) -> str:
2748    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2749        this = self.sql(expression, "this")
2750        kind = expression.text("kind")
2751        if kind:
2752            kind = f"{kind}."
2753        return f"@@{kind}{this}"
def placeholder_sql(self, expression: sqlglot.expressions.Placeholder) -> str:
2755    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2756        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
def subquery_sql(self, expression: sqlglot.expressions.Subquery, sep: str = ' AS ') -> str:
2758    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2759        alias = self.sql(expression, "alias")
2760        alias = f"{sep}{alias}" if alias else ""
2761        sample = self.sql(expression, "sample")
2762        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2763            alias = f"{sample}{alias}"
2764
2765            # Set to None so it's not generated again by self.query_modifiers()
2766            expression.set("sample", None)
2767
2768        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2769        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2770        return self.prepend_ctes(expression, sql)
def qualify_sql(self, expression: sqlglot.expressions.Qualify) -> str:
2772    def qualify_sql(self, expression: exp.Qualify) -> str:
2773        this = self.indent(self.sql(expression, "this"))
2774        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
def unnest_sql(self, expression: sqlglot.expressions.Unnest) -> str:
2776    def unnest_sql(self, expression: exp.Unnest) -> str:
2777        args = self.expressions(expression, flat=True)
2778
2779        alias = expression.args.get("alias")
2780        offset = expression.args.get("offset")
2781
2782        if self.UNNEST_WITH_ORDINALITY:
2783            if alias and isinstance(offset, exp.Expression):
2784                alias.append("columns", offset)
2785
2786        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2787            columns = alias.columns
2788            alias = self.sql(columns[0]) if columns else ""
2789        else:
2790            alias = self.sql(alias)
2791
2792        alias = f" AS {alias}" if alias else alias
2793        if self.UNNEST_WITH_ORDINALITY:
2794            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2795        else:
2796            if isinstance(offset, exp.Expression):
2797                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2798            elif offset:
2799                suffix = f"{alias} WITH OFFSET"
2800            else:
2801                suffix = alias
2802
2803        return f"UNNEST({args}){suffix}"
def prewhere_sql(self, expression: sqlglot.expressions.PreWhere) -> str:
2805    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2806        return ""
def where_sql(self, expression: sqlglot.expressions.Where) -> str:
2808    def where_sql(self, expression: exp.Where) -> str:
2809        this = self.indent(self.sql(expression, "this"))
2810        return f"{self.seg('WHERE')}{self.sep()}{this}"
def window_sql(self, expression: sqlglot.expressions.Window) -> str:
2812    def window_sql(self, expression: exp.Window) -> str:
2813        this = self.sql(expression, "this")
2814        partition = self.partition_by_sql(expression)
2815        order = expression.args.get("order")
2816        order = self.order_sql(order, flat=True) if order else ""
2817        spec = self.sql(expression, "spec")
2818        alias = self.sql(expression, "alias")
2819        over = self.sql(expression, "over") or "OVER"
2820
2821        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2822
2823        first = expression.args.get("first")
2824        if first is None:
2825            first = ""
2826        else:
2827            first = "FIRST" if first else "LAST"
2828
2829        if not partition and not order and not spec and alias:
2830            return f"{this} {alias}"
2831
2832        args = self.format_args(
2833            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2834        )
2835        return f"{this} ({args})"
def partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2837    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2838        partition = self.expressions(expression, key="partition_by", flat=True)
2839        return f"PARTITION BY {partition}" if partition else ""
def windowspec_sql(self, expression: sqlglot.expressions.WindowSpec) -> str:
2841    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2842        kind = self.sql(expression, "kind")
2843        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2844        end = (
2845            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2846            or "CURRENT ROW"
2847        )
2848
2849        window_spec = f"{kind} BETWEEN {start} AND {end}"
2850
2851        exclude = self.sql(expression, "exclude")
2852        if exclude:
2853            if self.SUPPORTS_WINDOW_EXCLUDE:
2854                window_spec += f" EXCLUDE {exclude}"
2855            else:
2856                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2857
2858        return window_spec
def withingroup_sql(self, expression: sqlglot.expressions.WithinGroup) -> str:
2860    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2861        this = self.sql(expression, "this")
2862        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2863        return f"{this} WITHIN GROUP ({expression_sql})"
def between_sql(self, expression: sqlglot.expressions.Between) -> str:
2865    def between_sql(self, expression: exp.Between) -> str:
2866        this = self.sql(expression, "this")
2867        low = self.sql(expression, "low")
2868        high = self.sql(expression, "high")
2869        return f"{this} BETWEEN {low} AND {high}"
def bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
2871    def bracket_offset_expressions(
2872        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2873    ) -> t.List[exp.Expression]:
2874        return apply_index_offset(
2875            expression.this,
2876            expression.expressions,
2877            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2878            dialect=self.dialect,
2879        )
def bracket_sql(self, expression: sqlglot.expressions.Bracket) -> str:
2881    def bracket_sql(self, expression: exp.Bracket) -> str:
2882        expressions = self.bracket_offset_expressions(expression)
2883        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2884        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
def all_sql(self, expression: sqlglot.expressions.All) -> str:
2886    def all_sql(self, expression: exp.All) -> str:
2887        return f"ALL {self.wrap(expression)}"
def any_sql(self, expression: sqlglot.expressions.Any) -> str:
2889    def any_sql(self, expression: exp.Any) -> str:
2890        this = self.sql(expression, "this")
2891        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2892            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2893                this = self.wrap(this)
2894            return f"ANY{this}"
2895        return f"ANY {this}"
def exists_sql(self, expression: sqlglot.expressions.Exists) -> str:
2897    def exists_sql(self, expression: exp.Exists) -> str:
2898        return f"EXISTS{self.wrap(expression)}"
def case_sql(self, expression: sqlglot.expressions.Case) -> str:
2900    def case_sql(self, expression: exp.Case) -> str:
2901        this = self.sql(expression, "this")
2902        statements = [f"CASE {this}" if this else "CASE"]
2903
2904        for e in expression.args["ifs"]:
2905            statements.append(f"WHEN {self.sql(e, 'this')}")
2906            statements.append(f"THEN {self.sql(e, 'true')}")
2907
2908        default = self.sql(expression, "default")
2909
2910        if default:
2911            statements.append(f"ELSE {default}")
2912
2913        statements.append("END")
2914
2915        if self.pretty and self.too_wide(statements):
2916            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2917
2918        return " ".join(statements)
def constraint_sql(self, expression: sqlglot.expressions.Constraint) -> str:
2920    def constraint_sql(self, expression: exp.Constraint) -> str:
2921        this = self.sql(expression, "this")
2922        expressions = self.expressions(expression, flat=True)
2923        return f"CONSTRAINT {this} {expressions}"
def nextvaluefor_sql(self, expression: sqlglot.expressions.NextValueFor) -> str:
2925    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2926        order = expression.args.get("order")
2927        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2928        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
def extract_sql(self, expression: sqlglot.expressions.Extract) -> str:
2930    def extract_sql(self, expression: exp.Extract) -> str:
2931        from sqlglot.dialects.dialect import map_date_part
2932
2933        this = (
2934            map_date_part(expression.this, self.dialect)
2935            if self.NORMALIZE_EXTRACT_DATE_PARTS
2936            else expression.this
2937        )
2938        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
2939        expression_sql = self.sql(expression, "expression")
2940
2941        return f"EXTRACT({this_sql} FROM {expression_sql})"
def trim_sql(self, expression: sqlglot.expressions.Trim) -> str:
2943    def trim_sql(self, expression: exp.Trim) -> str:
2944        trim_type = self.sql(expression, "position")
2945
2946        if trim_type == "LEADING":
2947            func_name = "LTRIM"
2948        elif trim_type == "TRAILING":
2949            func_name = "RTRIM"
2950        else:
2951            func_name = "TRIM"
2952
2953        return self.func(func_name, expression.this, expression.expression)
def convert_concat_args( self, expression: sqlglot.expressions.Concat | sqlglot.expressions.ConcatWs) -> List[sqlglot.expressions.Expression]:
2955    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
2956        args = expression.expressions
2957        if isinstance(expression, exp.ConcatWs):
2958            args = args[1:]  # Skip the delimiter
2959
2960        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
2961            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2962
2963        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2964            args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
2965
2966        return args
def concat_sql(self, expression: sqlglot.expressions.Concat) -> str:
2968    def concat_sql(self, expression: exp.Concat) -> str:
2969        expressions = self.convert_concat_args(expression)
2970
2971        # Some dialects don't allow a single-argument CONCAT call
2972        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
2973            return self.sql(expressions[0])
2974
2975        return self.func("CONCAT", *expressions)
def concatws_sql(self, expression: sqlglot.expressions.ConcatWs) -> str:
2977    def concatws_sql(self, expression: exp.ConcatWs) -> str:
2978        return self.func(
2979            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
2980        )
def check_sql(self, expression: sqlglot.expressions.Check) -> str:
2982    def check_sql(self, expression: exp.Check) -> str:
2983        this = self.sql(expression, key="this")
2984        return f"CHECK ({this})"
def foreignkey_sql(self, expression: sqlglot.expressions.ForeignKey) -> str:
2986    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
2987        expressions = self.expressions(expression, flat=True)
2988        expressions = f" ({expressions})" if expressions else ""
2989        reference = self.sql(expression, "reference")
2990        reference = f" {reference}" if reference else ""
2991        delete = self.sql(expression, "delete")
2992        delete = f" ON DELETE {delete}" if delete else ""
2993        update = self.sql(expression, "update")
2994        update = f" ON UPDATE {update}" if update else ""
2995        options = self.expressions(expression, key="options", flat=True, sep=" ")
2996        options = f" {options}" if options else ""
2997        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
def primarykey_sql(self, expression: sqlglot.expressions.ForeignKey) -> str:
2999    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
3000        expressions = self.expressions(expression, flat=True)
3001        options = self.expressions(expression, key="options", flat=True, sep=" ")
3002        options = f" {options}" if options else ""
3003        return f"PRIMARY KEY ({expressions}){options}"
def if_sql(self, expression: sqlglot.expressions.If) -> str:
3005    def if_sql(self, expression: exp.If) -> str:
3006        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
def matchagainst_sql(self, expression: sqlglot.expressions.MatchAgainst) -> str:
3008    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3009        modifier = expression.args.get("modifier")
3010        modifier = f" {modifier}" if modifier else ""
3011        return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
def jsonkeyvalue_sql(self, expression: sqlglot.expressions.JSONKeyValue) -> str:
3013    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3014        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
def jsonpath_sql(self, expression: sqlglot.expressions.JSONPath) -> str:
3016    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3017        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3018
3019        if expression.args.get("escape"):
3020            path = self.escape_str(path)
3021
3022        if self.QUOTE_JSON_PATH:
3023            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3024
3025        return path
def json_path_part(self, expression: int | str | sqlglot.expressions.JSONPathPart) -> str:
3027    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3028        if isinstance(expression, exp.JSONPathPart):
3029            transform = self.TRANSFORMS.get(expression.__class__)
3030            if not callable(transform):
3031                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3032                return ""
3033
3034            return transform(self, expression)
3035
3036        if isinstance(expression, int):
3037            return str(expression)
3038
3039        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3040            escaped = expression.replace("'", "\\'")
3041            escaped = f"\\'{expression}\\'"
3042        else:
3043            escaped = expression.replace('"', '\\"')
3044            escaped = f'"{escaped}"'
3045
3046        return escaped
def formatjson_sql(self, expression: sqlglot.expressions.FormatJson) -> str:
3048    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3049        return f"{self.sql(expression, 'this')} FORMAT JSON"
def formatphrase_sql(self, expression: sqlglot.expressions.FormatPhrase) -> str:
3051    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3052        # Output the Teradata column FORMAT override.
3053        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3054        this = self.sql(expression, "this")
3055        fmt = self.sql(expression, "format")
3056        return f"{this} (FORMAT {fmt})"
def jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3058    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3059        null_handling = expression.args.get("null_handling")
3060        null_handling = f" {null_handling}" if null_handling else ""
3061
3062        unique_keys = expression.args.get("unique_keys")
3063        if unique_keys is not None:
3064            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3065        else:
3066            unique_keys = ""
3067
3068        return_type = self.sql(expression, "return_type")
3069        return_type = f" RETURNING {return_type}" if return_type else ""
3070        encoding = self.sql(expression, "encoding")
3071        encoding = f" ENCODING {encoding}" if encoding else ""
3072
3073        return self.func(
3074            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3075            *expression.expressions,
3076            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3077        )
def jsonobjectagg_sql(self, expression: sqlglot.expressions.JSONObjectAgg) -> str:
3079    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3080        return self.jsonobject_sql(expression)
def jsonarray_sql(self, expression: sqlglot.expressions.JSONArray) -> str:
3082    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3083        null_handling = expression.args.get("null_handling")
3084        null_handling = f" {null_handling}" if null_handling else ""
3085        return_type = self.sql(expression, "return_type")
3086        return_type = f" RETURNING {return_type}" if return_type else ""
3087        strict = " STRICT" if expression.args.get("strict") else ""
3088        return self.func(
3089            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3090        )
def jsonarrayagg_sql(self, expression: sqlglot.expressions.JSONArrayAgg) -> str:
3092    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3093        this = self.sql(expression, "this")
3094        order = self.sql(expression, "order")
3095        null_handling = expression.args.get("null_handling")
3096        null_handling = f" {null_handling}" if null_handling else ""
3097        return_type = self.sql(expression, "return_type")
3098        return_type = f" RETURNING {return_type}" if return_type else ""
3099        strict = " STRICT" if expression.args.get("strict") else ""
3100        return self.func(
3101            "JSON_ARRAYAGG",
3102            this,
3103            suffix=f"{order}{null_handling}{return_type}{strict})",
3104        )
def jsoncolumndef_sql(self, expression: sqlglot.expressions.JSONColumnDef) -> str:
3106    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3107        path = self.sql(expression, "path")
3108        path = f" PATH {path}" if path else ""
3109        nested_schema = self.sql(expression, "nested_schema")
3110
3111        if nested_schema:
3112            return f"NESTED{path} {nested_schema}"
3113
3114        this = self.sql(expression, "this")
3115        kind = self.sql(expression, "kind")
3116        kind = f" {kind}" if kind else ""
3117        return f"{this}{kind}{path}"
def jsonschema_sql(self, expression: sqlglot.expressions.JSONSchema) -> str:
3119    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3120        return self.func("COLUMNS", *expression.expressions)
def jsontable_sql(self, expression: sqlglot.expressions.JSONTable) -> str:
3122    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3123        this = self.sql(expression, "this")
3124        path = self.sql(expression, "path")
3125        path = f", {path}" if path else ""
3126        error_handling = expression.args.get("error_handling")
3127        error_handling = f" {error_handling}" if error_handling else ""
3128        empty_handling = expression.args.get("empty_handling")
3129        empty_handling = f" {empty_handling}" if empty_handling else ""
3130        schema = self.sql(expression, "schema")
3131        return self.func(
3132            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3133        )
def openjsoncolumndef_sql(self, expression: sqlglot.expressions.OpenJSONColumnDef) -> str:
3135    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3136        this = self.sql(expression, "this")
3137        kind = self.sql(expression, "kind")
3138        path = self.sql(expression, "path")
3139        path = f" {path}" if path else ""
3140        as_json = " AS JSON" if expression.args.get("as_json") else ""
3141        return f"{this} {kind}{path}{as_json}"
def openjson_sql(self, expression: sqlglot.expressions.OpenJSON) -> str:
3143    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3144        this = self.sql(expression, "this")
3145        path = self.sql(expression, "path")
3146        path = f", {path}" if path else ""
3147        expressions = self.expressions(expression)
3148        with_ = (
3149            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3150            if expressions
3151            else ""
3152        )
3153        return f"OPENJSON({this}{path}){with_}"
def in_sql(self, expression: sqlglot.expressions.In) -> str:
3155    def in_sql(self, expression: exp.In) -> str:
3156        query = expression.args.get("query")
3157        unnest = expression.args.get("unnest")
3158        field = expression.args.get("field")
3159        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3160
3161        if query:
3162            in_sql = self.sql(query)
3163        elif unnest:
3164            in_sql = self.in_unnest_op(unnest)
3165        elif field:
3166            in_sql = self.sql(field)
3167        else:
3168            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3169
3170        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
def in_unnest_op(self, unnest: sqlglot.expressions.Unnest) -> str:
3172    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3173        return f"(SELECT {self.sql(unnest)})"
def interval_sql(self, expression: sqlglot.expressions.Interval) -> str:
3175    def interval_sql(self, expression: exp.Interval) -> str:
3176        unit = self.sql(expression, "unit")
3177        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3178            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3179        unit = f" {unit}" if unit else ""
3180
3181        if self.SINGLE_STRING_INTERVAL:
3182            this = expression.this.name if expression.this else ""
3183            return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3184
3185        this = self.sql(expression, "this")
3186        if this:
3187            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3188            this = f" {this}" if unwrapped else f" ({this})"
3189
3190        return f"INTERVAL{this}{unit}"
def return_sql(self, expression: sqlglot.expressions.Return) -> str:
3192    def return_sql(self, expression: exp.Return) -> str:
3193        return f"RETURN {self.sql(expression, 'this')}"
def reference_sql(self, expression: sqlglot.expressions.Reference) -> str:
3195    def reference_sql(self, expression: exp.Reference) -> str:
3196        this = self.sql(expression, "this")
3197        expressions = self.expressions(expression, flat=True)
3198        expressions = f"({expressions})" if expressions else ""
3199        options = self.expressions(expression, key="options", flat=True, sep=" ")
3200        options = f" {options}" if options else ""
3201        return f"REFERENCES {this}{expressions}{options}"
def anonymous_sql(self, expression: sqlglot.expressions.Anonymous) -> str:
3203    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3204        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3205        parent = expression.parent
3206        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3207        return self.func(
3208            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3209        )
def paren_sql(self, expression: sqlglot.expressions.Paren) -> str:
3211    def paren_sql(self, expression: exp.Paren) -> str:
3212        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3213        return f"({sql}{self.seg(')', sep='')}"
def neg_sql(self, expression: sqlglot.expressions.Neg) -> str:
3215    def neg_sql(self, expression: exp.Neg) -> str:
3216        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3217        this_sql = self.sql(expression, "this")
3218        sep = " " if this_sql[0] == "-" else ""
3219        return f"-{sep}{this_sql}"
def not_sql(self, expression: sqlglot.expressions.Not) -> str:
3221    def not_sql(self, expression: exp.Not) -> str:
3222        return f"NOT {self.sql(expression, 'this')}"
def alias_sql(self, expression: sqlglot.expressions.Alias) -> str:
3224    def alias_sql(self, expression: exp.Alias) -> str:
3225        alias = self.sql(expression, "alias")
3226        alias = f" AS {alias}" if alias else ""
3227        return f"{self.sql(expression, 'this')}{alias}"
def pivotalias_sql(self, expression: sqlglot.expressions.PivotAlias) -> str:
3229    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3230        alias = expression.args["alias"]
3231
3232        parent = expression.parent
3233        pivot = parent and parent.parent
3234
3235        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3236            identifier_alias = isinstance(alias, exp.Identifier)
3237            literal_alias = isinstance(alias, exp.Literal)
3238
3239            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3240                alias.replace(exp.Literal.string(alias.output_name))
3241            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3242                alias.replace(exp.to_identifier(alias.output_name))
3243
3244        return self.alias_sql(expression)
def aliases_sql(self, expression: sqlglot.expressions.Aliases) -> str:
3246    def aliases_sql(self, expression: exp.Aliases) -> str:
3247        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
def atindex_sql(self, expression: sqlglot.expressions.AtTimeZone) -> str:
3249    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3250        this = self.sql(expression, "this")
3251        index = self.sql(expression, "expression")
3252        return f"{this} AT {index}"
def attimezone_sql(self, expression: sqlglot.expressions.AtTimeZone) -> str:
3254    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3255        this = self.sql(expression, "this")
3256        zone = self.sql(expression, "zone")
3257        return f"{this} AT TIME ZONE {zone}"
def fromtimezone_sql(self, expression: sqlglot.expressions.FromTimeZone) -> str:
3259    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3260        this = self.sql(expression, "this")
3261        zone = self.sql(expression, "zone")
3262        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
def add_sql(self, expression: sqlglot.expressions.Add) -> str:
3264    def add_sql(self, expression: exp.Add) -> str:
3265        return self.binary(expression, "+")
def and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3267    def and_sql(
3268        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3269    ) -> str:
3270        return self.connector_sql(expression, "AND", stack)
def or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3272    def or_sql(
3273        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3274    ) -> str:
3275        return self.connector_sql(expression, "OR", stack)
def xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3277    def xor_sql(
3278        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3279    ) -> str:
3280        return self.connector_sql(expression, "XOR", stack)
def connector_sql( self, expression: sqlglot.expressions.Connector, op: str, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3282    def connector_sql(
3283        self,
3284        expression: exp.Connector,
3285        op: str,
3286        stack: t.Optional[t.List[str | exp.Expression]] = None,
3287    ) -> str:
3288        if stack is not None:
3289            if expression.expressions:
3290                stack.append(self.expressions(expression, sep=f" {op} "))
3291            else:
3292                stack.append(expression.right)
3293                if expression.comments and self.comments:
3294                    for comment in expression.comments:
3295                        if comment:
3296                            op += f" /*{self.sanitize_comment(comment)}*/"
3297                stack.extend((op, expression.left))
3298            return op
3299
3300        stack = [expression]
3301        sqls: t.List[str] = []
3302        ops = set()
3303
3304        while stack:
3305            node = stack.pop()
3306            if isinstance(node, exp.Connector):
3307                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3308            else:
3309                sql = self.sql(node)
3310                if sqls and sqls[-1] in ops:
3311                    sqls[-1] += f" {sql}"
3312                else:
3313                    sqls.append(sql)
3314
3315        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3316        return sep.join(sqls)
def bitwiseand_sql(self, expression: sqlglot.expressions.BitwiseAnd) -> str:
3318    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3319        return self.binary(expression, "&")
def bitwiseleftshift_sql(self, expression: sqlglot.expressions.BitwiseLeftShift) -> str:
3321    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3322        return self.binary(expression, "<<")
def bitwisenot_sql(self, expression: sqlglot.expressions.BitwiseNot) -> str:
3324    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3325        return f"~{self.sql(expression, 'this')}"
def bitwiseor_sql(self, expression: sqlglot.expressions.BitwiseOr) -> str:
3327    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3328        return self.binary(expression, "|")
def bitwiserightshift_sql(self, expression: sqlglot.expressions.BitwiseRightShift) -> str:
3330    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3331        return self.binary(expression, ">>")
def bitwisexor_sql(self, expression: sqlglot.expressions.BitwiseXor) -> str:
3333    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3334        return self.binary(expression, "^")
def cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3336    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3337        format_sql = self.sql(expression, "format")
3338        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3339        to_sql = self.sql(expression, "to")
3340        to_sql = f" {to_sql}" if to_sql else ""
3341        action = self.sql(expression, "action")
3342        action = f" {action}" if action else ""
3343        default = self.sql(expression, "default")
3344        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3345        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
def currentdate_sql(self, expression: sqlglot.expressions.CurrentDate) -> str:
3347    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3348        zone = self.sql(expression, "this")
3349        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
def collate_sql(self, expression: sqlglot.expressions.Collate) -> str:
3351    def collate_sql(self, expression: exp.Collate) -> str:
3352        if self.COLLATE_IS_FUNC:
3353            return self.function_fallback_sql(expression)
3354        return self.binary(expression, "COLLATE")
def command_sql(self, expression: sqlglot.expressions.Command) -> str:
3356    def command_sql(self, expression: exp.Command) -> str:
3357        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
def comment_sql(self, expression: sqlglot.expressions.Comment) -> str:
3359    def comment_sql(self, expression: exp.Comment) -> str:
3360        this = self.sql(expression, "this")
3361        kind = expression.args["kind"]
3362        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3363        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3364        expression_sql = self.sql(expression, "expression")
3365        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
def mergetreettlaction_sql(self, expression: sqlglot.expressions.MergeTreeTTLAction) -> str:
3367    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3368        this = self.sql(expression, "this")
3369        delete = " DELETE" if expression.args.get("delete") else ""
3370        recompress = self.sql(expression, "recompress")
3371        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3372        to_disk = self.sql(expression, "to_disk")
3373        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3374        to_volume = self.sql(expression, "to_volume")
3375        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3376        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
def mergetreettl_sql(self, expression: sqlglot.expressions.MergeTreeTTL) -> str:
3378    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3379        where = self.sql(expression, "where")
3380        group = self.sql(expression, "group")
3381        aggregates = self.expressions(expression, key="aggregates")
3382        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3383
3384        if not (where or group or aggregates) and len(expression.expressions) == 1:
3385            return f"TTL {self.expressions(expression, flat=True)}"
3386
3387        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
def transaction_sql(self, expression: sqlglot.expressions.Transaction) -> str:
3389    def transaction_sql(self, expression: exp.Transaction) -> str:
3390        return "BEGIN"
def commit_sql(self, expression: sqlglot.expressions.Commit) -> str:
3392    def commit_sql(self, expression: exp.Commit) -> str:
3393        chain = expression.args.get("chain")
3394        if chain is not None:
3395            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3396
3397        return f"COMMIT{chain or ''}"
def rollback_sql(self, expression: sqlglot.expressions.Rollback) -> str:
3399    def rollback_sql(self, expression: exp.Rollback) -> str:
3400        savepoint = expression.args.get("savepoint")
3401        savepoint = f" TO {savepoint}" if savepoint else ""
3402        return f"ROLLBACK{savepoint}"
def altercolumn_sql(self, expression: sqlglot.expressions.AlterColumn) -> str:
3404    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3405        this = self.sql(expression, "this")
3406
3407        dtype = self.sql(expression, "dtype")
3408        if dtype:
3409            collate = self.sql(expression, "collate")
3410            collate = f" COLLATE {collate}" if collate else ""
3411            using = self.sql(expression, "using")
3412            using = f" USING {using}" if using else ""
3413            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3414            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3415
3416        default = self.sql(expression, "default")
3417        if default:
3418            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3419
3420        comment = self.sql(expression, "comment")
3421        if comment:
3422            return f"ALTER COLUMN {this} COMMENT {comment}"
3423
3424        visible = expression.args.get("visible")
3425        if visible:
3426            return f"ALTER COLUMN {this} SET {visible}"
3427
3428        allow_null = expression.args.get("allow_null")
3429        drop = expression.args.get("drop")
3430
3431        if not drop and not allow_null:
3432            self.unsupported("Unsupported ALTER COLUMN syntax")
3433
3434        if allow_null is not None:
3435            keyword = "DROP" if drop else "SET"
3436            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3437
3438        return f"ALTER COLUMN {this} DROP DEFAULT"
def alterindex_sql(self, expression: sqlglot.expressions.AlterIndex) -> str:
3440    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3441        this = self.sql(expression, "this")
3442
3443        visible = expression.args.get("visible")
3444        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3445
3446        return f"ALTER INDEX {this} {visible_sql}"
def alterdiststyle_sql(self, expression: sqlglot.expressions.AlterDistStyle) -> str:
3448    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3449        this = self.sql(expression, "this")
3450        if not isinstance(expression.this, exp.Var):
3451            this = f"KEY DISTKEY {this}"
3452        return f"ALTER DISTSTYLE {this}"
def altersortkey_sql(self, expression: sqlglot.expressions.AlterSortKey) -> str:
3454    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3455        compound = " COMPOUND" if expression.args.get("compound") else ""
3456        this = self.sql(expression, "this")
3457        expressions = self.expressions(expression, flat=True)
3458        expressions = f"({expressions})" if expressions else ""
3459        return f"ALTER{compound} SORTKEY {this or expressions}"
def alterrename_sql(self, expression: sqlglot.expressions.AlterRename) -> str:
3461    def alterrename_sql(self, expression: exp.AlterRename) -> str:
3462        if not self.RENAME_TABLE_WITH_DB:
3463            # Remove db from tables
3464            expression = expression.transform(
3465                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3466            ).assert_is(exp.AlterRename)
3467        this = self.sql(expression, "this")
3468        return f"RENAME TO {this}"
def renamecolumn_sql(self, expression: sqlglot.expressions.RenameColumn) -> str:
3470    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3471        exists = " IF EXISTS" if expression.args.get("exists") else ""
3472        old_column = self.sql(expression, "this")
3473        new_column = self.sql(expression, "to")
3474        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
def alterset_sql(self, expression: sqlglot.expressions.AlterSet) -> str:
3476    def alterset_sql(self, expression: exp.AlterSet) -> str:
3477        exprs = self.expressions(expression, flat=True)
3478        if self.ALTER_SET_WRAPPED:
3479            exprs = f"({exprs})"
3480
3481        return f"SET {exprs}"
def alter_sql(self, expression: sqlglot.expressions.Alter) -> str:
3483    def alter_sql(self, expression: exp.Alter) -> str:
3484        actions = expression.args["actions"]
3485
3486        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3487            actions[0], exp.ColumnDef
3488        ):
3489            actions_sql = self.expressions(expression, key="actions", flat=True)
3490            actions_sql = f"ADD {actions_sql}"
3491        else:
3492            actions_list = []
3493            for action in actions:
3494                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3495                    action_sql = self.add_column_sql(action)
3496                else:
3497                    action_sql = self.sql(action)
3498                    if isinstance(action, exp.Query):
3499                        action_sql = f"AS {action_sql}"
3500
3501                actions_list.append(action_sql)
3502
3503            actions_sql = self.format_args(*actions_list).lstrip("\n")
3504
3505        exists = " IF EXISTS" if expression.args.get("exists") else ""
3506        on_cluster = self.sql(expression, "cluster")
3507        on_cluster = f" {on_cluster}" if on_cluster else ""
3508        only = " ONLY" if expression.args.get("only") else ""
3509        options = self.expressions(expression, key="options")
3510        options = f", {options}" if options else ""
3511        kind = self.sql(expression, "kind")
3512        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3513
3514        return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
def add_column_sql(self, expression: sqlglot.expressions.Expression) -> str:
3516    def add_column_sql(self, expression: exp.Expression) -> str:
3517        sql = self.sql(expression)
3518        if isinstance(expression, exp.Schema):
3519            column_text = " COLUMNS"
3520        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3521            column_text = " COLUMN"
3522        else:
3523            column_text = ""
3524
3525        return f"ADD{column_text} {sql}"
def droppartition_sql(self, expression: sqlglot.expressions.DropPartition) -> str:
3527    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3528        expressions = self.expressions(expression)
3529        exists = " IF EXISTS " if expression.args.get("exists") else " "
3530        return f"DROP{exists}{expressions}"
def addconstraint_sql(self, expression: sqlglot.expressions.AddConstraint) -> str:
3532    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3533        return f"ADD {self.expressions(expression, indent=False)}"
def addpartition_sql(self, expression: sqlglot.expressions.AddPartition) -> str:
3535    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3536        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3537        return f"ADD {exists}{self.sql(expression.this)}"
def distinct_sql(self, expression: sqlglot.expressions.Distinct) -> str:
3539    def distinct_sql(self, expression: exp.Distinct) -> str:
3540        this = self.expressions(expression, flat=True)
3541
3542        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3543            case = exp.case()
3544            for arg in expression.expressions:
3545                case = case.when(arg.is_(exp.null()), exp.null())
3546            this = self.sql(case.else_(f"({this})"))
3547
3548        this = f" {this}" if this else ""
3549
3550        on = self.sql(expression, "on")
3551        on = f" ON {on}" if on else ""
3552        return f"DISTINCT{this}{on}"
def ignorenulls_sql(self, expression: sqlglot.expressions.IgnoreNulls) -> str:
3554    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3555        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
def respectnulls_sql(self, expression: sqlglot.expressions.RespectNulls) -> str:
3557    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3558        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
def havingmax_sql(self, expression: sqlglot.expressions.HavingMax) -> str:
3560    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3561        this_sql = self.sql(expression, "this")
3562        expression_sql = self.sql(expression, "expression")
3563        kind = "MAX" if expression.args.get("max") else "MIN"
3564        return f"{this_sql} HAVING {kind} {expression_sql}"
def intdiv_sql(self, expression: sqlglot.expressions.IntDiv) -> str:
3566    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3567        return self.sql(
3568            exp.Cast(
3569                this=exp.Div(this=expression.this, expression=expression.expression),
3570                to=exp.DataType(this=exp.DataType.Type.INT),
3571            )
3572        )
def dpipe_sql(self, expression: sqlglot.expressions.DPipe) -> str:
3574    def dpipe_sql(self, expression: exp.DPipe) -> str:
3575        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3576            return self.func(
3577                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3578            )
3579        return self.binary(expression, "||")
def div_sql(self, expression: sqlglot.expressions.Div) -> str:
3581    def div_sql(self, expression: exp.Div) -> str:
3582        l, r = expression.left, expression.right
3583
3584        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3585            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3586
3587        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3588            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3589                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3590
3591        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3592            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3593                return self.sql(
3594                    exp.cast(
3595                        l / r,
3596                        to=exp.DataType.Type.BIGINT,
3597                    )
3598                )
3599
3600        return self.binary(expression, "/")
def safedivide_sql(self, expression: sqlglot.expressions.SafeDivide) -> str:
3602    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3603        n = exp._wrap(expression.this, exp.Binary)
3604        d = exp._wrap(expression.expression, exp.Binary)
3605        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
def overlaps_sql(self, expression: sqlglot.expressions.Overlaps) -> str:
3607    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3608        return self.binary(expression, "OVERLAPS")
def distance_sql(self, expression: sqlglot.expressions.Distance) -> str:
3610    def distance_sql(self, expression: exp.Distance) -> str:
3611        return self.binary(expression, "<->")
def dot_sql(self, expression: sqlglot.expressions.Dot) -> str:
3613    def dot_sql(self, expression: exp.Dot) -> str:
3614        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
def eq_sql(self, expression: sqlglot.expressions.EQ) -> str:
3616    def eq_sql(self, expression: exp.EQ) -> str:
3617        return self.binary(expression, "=")
def propertyeq_sql(self, expression: sqlglot.expressions.PropertyEQ) -> str:
3619    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3620        return self.binary(expression, ":=")
def escape_sql(self, expression: sqlglot.expressions.Escape) -> str:
3622    def escape_sql(self, expression: exp.Escape) -> str:
3623        return self.binary(expression, "ESCAPE")
def glob_sql(self, expression: sqlglot.expressions.Glob) -> str:
3625    def glob_sql(self, expression: exp.Glob) -> str:
3626        return self.binary(expression, "GLOB")
def gt_sql(self, expression: sqlglot.expressions.GT) -> str:
3628    def gt_sql(self, expression: exp.GT) -> str:
3629        return self.binary(expression, ">")
def gte_sql(self, expression: sqlglot.expressions.GTE) -> str:
3631    def gte_sql(self, expression: exp.GTE) -> str:
3632        return self.binary(expression, ">=")
def ilike_sql(self, expression: sqlglot.expressions.ILike) -> str:
3634    def ilike_sql(self, expression: exp.ILike) -> str:
3635        return self.binary(expression, "ILIKE")
def ilikeany_sql(self, expression: sqlglot.expressions.ILikeAny) -> str:
3637    def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
3638        return self.binary(expression, "ILIKE ANY")
def is_sql(self, expression: sqlglot.expressions.Is) -> str:
3640    def is_sql(self, expression: exp.Is) -> str:
3641        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3642            return self.sql(
3643                expression.this if expression.expression.this else exp.not_(expression.this)
3644            )
3645        return self.binary(expression, "IS")
def like_sql(self, expression: sqlglot.expressions.Like) -> str:
3647    def like_sql(self, expression: exp.Like) -> str:
3648        return self.binary(expression, "LIKE")
def likeany_sql(self, expression: sqlglot.expressions.LikeAny) -> str:
3650    def likeany_sql(self, expression: exp.LikeAny) -> str:
3651        return self.binary(expression, "LIKE ANY")
def similarto_sql(self, expression: sqlglot.expressions.SimilarTo) -> str:
3653    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3654        return self.binary(expression, "SIMILAR TO")
def lt_sql(self, expression: sqlglot.expressions.LT) -> str:
3656    def lt_sql(self, expression: exp.LT) -> str:
3657        return self.binary(expression, "<")
def lte_sql(self, expression: sqlglot.expressions.LTE) -> str:
3659    def lte_sql(self, expression: exp.LTE) -> str:
3660        return self.binary(expression, "<=")
def mod_sql(self, expression: sqlglot.expressions.Mod) -> str:
3662    def mod_sql(self, expression: exp.Mod) -> str:
3663        return self.binary(expression, "%")
def mul_sql(self, expression: sqlglot.expressions.Mul) -> str:
3665    def mul_sql(self, expression: exp.Mul) -> str:
3666        return self.binary(expression, "*")
def neq_sql(self, expression: sqlglot.expressions.NEQ) -> str:
3668    def neq_sql(self, expression: exp.NEQ) -> str:
3669        return self.binary(expression, "<>")
def nullsafeeq_sql(self, expression: sqlglot.expressions.NullSafeEQ) -> str:
3671    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3672        return self.binary(expression, "IS NOT DISTINCT FROM")
def nullsafeneq_sql(self, expression: sqlglot.expressions.NullSafeNEQ) -> str:
3674    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3675        return self.binary(expression, "IS DISTINCT FROM")
def slice_sql(self, expression: sqlglot.expressions.Slice) -> str:
3677    def slice_sql(self, expression: exp.Slice) -> str:
3678        return self.binary(expression, ":")
def sub_sql(self, expression: sqlglot.expressions.Sub) -> str:
3680    def sub_sql(self, expression: exp.Sub) -> str:
3681        return self.binary(expression, "-")
def trycast_sql(self, expression: sqlglot.expressions.TryCast) -> str:
3683    def trycast_sql(self, expression: exp.TryCast) -> str:
3684        return self.cast_sql(expression, safe_prefix="TRY_")
def jsoncast_sql(self, expression: sqlglot.expressions.JSONCast) -> str:
3686    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3687        return self.cast_sql(expression)
def try_sql(self, expression: sqlglot.expressions.Try) -> str:
3689    def try_sql(self, expression: exp.Try) -> str:
3690        if not self.TRY_SUPPORTED:
3691            self.unsupported("Unsupported TRY function")
3692            return self.sql(expression, "this")
3693
3694        return self.func("TRY", expression.this)
def log_sql(self, expression: sqlglot.expressions.Log) -> str:
3696    def log_sql(self, expression: exp.Log) -> str:
3697        this = expression.this
3698        expr = expression.expression
3699
3700        if self.dialect.LOG_BASE_FIRST is False:
3701            this, expr = expr, this
3702        elif self.dialect.LOG_BASE_FIRST is None and expr:
3703            if this.name in ("2", "10"):
3704                return self.func(f"LOG{this.name}", expr)
3705
3706            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3707
3708        return self.func("LOG", this, expr)
def use_sql(self, expression: sqlglot.expressions.Use) -> str:
3710    def use_sql(self, expression: exp.Use) -> str:
3711        kind = self.sql(expression, "kind")
3712        kind = f" {kind}" if kind else ""
3713        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3714        this = f" {this}" if this else ""
3715        return f"USE{kind}{this}"
def binary(self, expression: sqlglot.expressions.Binary, op: str) -> str:
3717    def binary(self, expression: exp.Binary, op: str) -> str:
3718        sqls: t.List[str] = []
3719        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3720        binary_type = type(expression)
3721
3722        while stack:
3723            node = stack.pop()
3724
3725            if type(node) is binary_type:
3726                op_func = node.args.get("operator")
3727                if op_func:
3728                    op = f"OPERATOR({self.sql(op_func)})"
3729
3730                stack.append(node.right)
3731                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3732                stack.append(node.left)
3733            else:
3734                sqls.append(self.sql(node))
3735
3736        return "".join(sqls)
def ceil_floor( self, expression: sqlglot.expressions.Ceil | sqlglot.expressions.Floor) -> str:
3738    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3739        to_clause = self.sql(expression, "to")
3740        if to_clause:
3741            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3742
3743        return self.function_fallback_sql(expression)
def function_fallback_sql(self, expression: sqlglot.expressions.Func) -> str:
3745    def function_fallback_sql(self, expression: exp.Func) -> str:
3746        args = []
3747
3748        for key in expression.arg_types:
3749            arg_value = expression.args.get(key)
3750
3751            if isinstance(arg_value, list):
3752                for value in arg_value:
3753                    args.append(value)
3754            elif arg_value is not None:
3755                args.append(arg_value)
3756
3757        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3758            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3759        else:
3760            name = expression.sql_name()
3761
3762        return self.func(name, *args)
def func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
3764    def func(
3765        self,
3766        name: str,
3767        *args: t.Optional[exp.Expression | str],
3768        prefix: str = "(",
3769        suffix: str = ")",
3770        normalize: bool = True,
3771    ) -> str:
3772        name = self.normalize_func(name) if normalize else name
3773        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3775    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3776        arg_sqls = tuple(
3777            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3778        )
3779        if self.pretty and self.too_wide(arg_sqls):
3780            return self.indent(
3781                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3782            )
3783        return sep.join(arg_sqls)
def too_wide(self, args: Iterable) -> bool:
3785    def too_wide(self, args: t.Iterable) -> bool:
3786        return sum(len(arg) for arg in args) > self.max_text_width
def format_time( self, expression: sqlglot.expressions.Expression, inverse_time_mapping: Optional[Dict[str, str]] = None, inverse_time_trie: Optional[Dict] = None) -> Optional[str]:
3788    def format_time(
3789        self,
3790        expression: exp.Expression,
3791        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3792        inverse_time_trie: t.Optional[t.Dict] = None,
3793    ) -> t.Optional[str]:
3794        return format_time(
3795            self.sql(expression, "format"),
3796            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3797            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3798        )
def expressions( self, expression: Optional[sqlglot.expressions.Expression] = None, key: Optional[str] = None, sqls: Optional[Collection[Union[str, sqlglot.expressions.Expression]]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
3800    def expressions(
3801        self,
3802        expression: t.Optional[exp.Expression] = None,
3803        key: t.Optional[str] = None,
3804        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3805        flat: bool = False,
3806        indent: bool = True,
3807        skip_first: bool = False,
3808        skip_last: bool = False,
3809        sep: str = ", ",
3810        prefix: str = "",
3811        dynamic: bool = False,
3812        new_line: bool = False,
3813    ) -> str:
3814        expressions = expression.args.get(key or "expressions") if expression else sqls
3815
3816        if not expressions:
3817            return ""
3818
3819        if flat:
3820            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3821
3822        num_sqls = len(expressions)
3823        result_sqls = []
3824
3825        for i, e in enumerate(expressions):
3826            sql = self.sql(e, comment=False)
3827            if not sql:
3828                continue
3829
3830            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3831
3832            if self.pretty:
3833                if self.leading_comma:
3834                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3835                else:
3836                    result_sqls.append(
3837                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3838                    )
3839            else:
3840                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3841
3842        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3843            if new_line:
3844                result_sqls.insert(0, "")
3845                result_sqls.append("")
3846            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3847        else:
3848            result_sql = "".join(result_sqls)
3849
3850        return (
3851            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
3852            if indent
3853            else result_sql
3854        )
def op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3856    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
3857        flat = flat or isinstance(expression.parent, exp.Properties)
3858        expressions_sql = self.expressions(expression, flat=flat)
3859        if flat:
3860            return f"{op} {expressions_sql}"
3861        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
def naked_property(self, expression: sqlglot.expressions.Property) -> str:
3863    def naked_property(self, expression: exp.Property) -> str:
3864        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
3865        if not property_name:
3866            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
3867        return f"{property_name} {self.sql(expression, 'this')}"
def tag_sql(self, expression: sqlglot.expressions.Tag) -> str:
3869    def tag_sql(self, expression: exp.Tag) -> str:
3870        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
def token_sql(self, token_type: sqlglot.tokens.TokenType) -> str:
3872    def token_sql(self, token_type: TokenType) -> str:
3873        return self.TOKEN_MAPPING.get(token_type, token_type.name)
def userdefinedfunction_sql(self, expression: sqlglot.expressions.UserDefinedFunction) -> str:
3875    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
3876        this = self.sql(expression, "this")
3877        expressions = self.no_identify(self.expressions, expression)
3878        expressions = (
3879            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
3880        )
3881        return f"{this}{expressions}" if expressions.strip() != "" else this
def joinhint_sql(self, expression: sqlglot.expressions.JoinHint) -> str:
3883    def joinhint_sql(self, expression: exp.JoinHint) -> str:
3884        this = self.sql(expression, "this")
3885        expressions = self.expressions(expression, flat=True)
3886        return f"{this}({expressions})"
def kwarg_sql(self, expression: sqlglot.expressions.Kwarg) -> str:
3888    def kwarg_sql(self, expression: exp.Kwarg) -> str:
3889        return self.binary(expression, "=>")
def when_sql(self, expression: sqlglot.expressions.When) -> str:
3891    def when_sql(self, expression: exp.When) -> str:
3892        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
3893        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
3894        condition = self.sql(expression, "condition")
3895        condition = f" AND {condition}" if condition else ""
3896
3897        then_expression = expression.args.get("then")
3898        if isinstance(then_expression, exp.Insert):
3899            this = self.sql(then_expression, "this")
3900            this = f"INSERT {this}" if this else "INSERT"
3901            then = self.sql(then_expression, "expression")
3902            then = f"{this} VALUES {then}" if then else this
3903        elif isinstance(then_expression, exp.Update):
3904            if isinstance(then_expression.args.get("expressions"), exp.Star):
3905                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
3906            else:
3907                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
3908        else:
3909            then = self.sql(then_expression)
3910        return f"WHEN {matched}{source}{condition} THEN {then}"
def whens_sql(self, expression: sqlglot.expressions.Whens) -> str:
3912    def whens_sql(self, expression: exp.Whens) -> str:
3913        return self.expressions(expression, sep=" ", indent=False)
def merge_sql(self, expression: sqlglot.expressions.Merge) -> str:
3915    def merge_sql(self, expression: exp.Merge) -> str:
3916        table = expression.this
3917        table_alias = ""
3918
3919        hints = table.args.get("hints")
3920        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
3921            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
3922            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
3923
3924        this = self.sql(table)
3925        using = f"USING {self.sql(expression, 'using')}"
3926        on = f"ON {self.sql(expression, 'on')}"
3927        whens = self.sql(expression, "whens")
3928
3929        returning = self.sql(expression, "returning")
3930        if returning:
3931            whens = f"{whens}{returning}"
3932
3933        sep = self.sep()
3934
3935        return self.prepend_ctes(
3936            expression,
3937            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
3938        )
@unsupported_args('format')
def tochar_sql(self, expression: sqlglot.expressions.ToChar) -> str:
3940    @unsupported_args("format")
3941    def tochar_sql(self, expression: exp.ToChar) -> str:
3942        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
def tonumber_sql(self, expression: sqlglot.expressions.ToNumber) -> str:
3944    def tonumber_sql(self, expression: exp.ToNumber) -> str:
3945        if not self.SUPPORTS_TO_NUMBER:
3946            self.unsupported("Unsupported TO_NUMBER function")
3947            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3948
3949        fmt = expression.args.get("format")
3950        if not fmt:
3951            self.unsupported("Conversion format is required for TO_NUMBER")
3952            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3953
3954        return self.func("TO_NUMBER", expression.this, fmt)
def dictproperty_sql(self, expression: sqlglot.expressions.DictProperty) -> str:
3956    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
3957        this = self.sql(expression, "this")
3958        kind = self.sql(expression, "kind")
3959        settings_sql = self.expressions(expression, key="settings", sep=" ")
3960        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
3961        return f"{this}({kind}{args})"
def dictrange_sql(self, expression: sqlglot.expressions.DictRange) -> str:
3963    def dictrange_sql(self, expression: exp.DictRange) -> str:
3964        this = self.sql(expression, "this")
3965        max = self.sql(expression, "max")
3966        min = self.sql(expression, "min")
3967        return f"{this}(MIN {min} MAX {max})"
def dictsubproperty_sql(self, expression: sqlglot.expressions.DictSubProperty) -> str:
3969    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
3970        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
def duplicatekeyproperty_sql(self, expression: sqlglot.expressions.DuplicateKeyProperty) -> str:
3972    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
3973        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
def uniquekeyproperty_sql(self, expression: sqlglot.expressions.UniqueKeyProperty) -> str:
3976    def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
3977        return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
def distributedbyproperty_sql(self, expression: sqlglot.expressions.DistributedByProperty) -> str:
3980    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
3981        expressions = self.expressions(expression, flat=True)
3982        expressions = f" {self.wrap(expressions)}" if expressions else ""
3983        buckets = self.sql(expression, "buckets")
3984        kind = self.sql(expression, "kind")
3985        buckets = f" BUCKETS {buckets}" if buckets else ""
3986        order = self.sql(expression, "order")
3987        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def oncluster_sql(self, expression: sqlglot.expressions.OnCluster) -> str:
3989    def oncluster_sql(self, expression: exp.OnCluster) -> str:
3990        return ""
def clusteredbyproperty_sql(self, expression: sqlglot.expressions.ClusteredByProperty) -> str:
3992    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
3993        expressions = self.expressions(expression, key="expressions", flat=True)
3994        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
3995        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
3996        buckets = self.sql(expression, "buckets")
3997        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
def anyvalue_sql(self, expression: sqlglot.expressions.AnyValue) -> str:
3999    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4000        this = self.sql(expression, "this")
4001        having = self.sql(expression, "having")
4002
4003        if having:
4004            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4005
4006        return self.func("ANY_VALUE", this)
def querytransform_sql(self, expression: sqlglot.expressions.QueryTransform) -> str:
4008    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4009        transform = self.func("TRANSFORM", *expression.expressions)
4010        row_format_before = self.sql(expression, "row_format_before")
4011        row_format_before = f" {row_format_before}" if row_format_before else ""
4012        record_writer = self.sql(expression, "record_writer")
4013        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4014        using = f" USING {self.sql(expression, 'command_script')}"
4015        schema = self.sql(expression, "schema")
4016        schema = f" AS {schema}" if schema else ""
4017        row_format_after = self.sql(expression, "row_format_after")
4018        row_format_after = f" {row_format_after}" if row_format_after else ""
4019        record_reader = self.sql(expression, "record_reader")
4020        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4021        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
def indexconstraintoption_sql(self, expression: sqlglot.expressions.IndexConstraintOption) -> str:
4023    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4024        key_block_size = self.sql(expression, "key_block_size")
4025        if key_block_size:
4026            return f"KEY_BLOCK_SIZE = {key_block_size}"
4027
4028        using = self.sql(expression, "using")
4029        if using:
4030            return f"USING {using}"
4031
4032        parser = self.sql(expression, "parser")
4033        if parser:
4034            return f"WITH PARSER {parser}"
4035
4036        comment = self.sql(expression, "comment")
4037        if comment:
4038            return f"COMMENT {comment}"
4039
4040        visible = expression.args.get("visible")
4041        if visible is not None:
4042            return "VISIBLE" if visible else "INVISIBLE"
4043
4044        engine_attr = self.sql(expression, "engine_attr")
4045        if engine_attr:
4046            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4047
4048        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4049        if secondary_engine_attr:
4050            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4051
4052        self.unsupported("Unsupported index constraint option.")
4053        return ""
def checkcolumnconstraint_sql(self, expression: sqlglot.expressions.CheckColumnConstraint) -> str:
4055    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4056        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4057        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
def indexcolumnconstraint_sql(self, expression: sqlglot.expressions.IndexColumnConstraint) -> str:
4059    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4060        kind = self.sql(expression, "kind")
4061        kind = f"{kind} INDEX" if kind else "INDEX"
4062        this = self.sql(expression, "this")
4063        this = f" {this}" if this else ""
4064        index_type = self.sql(expression, "index_type")
4065        index_type = f" USING {index_type}" if index_type else ""
4066        expressions = self.expressions(expression, flat=True)
4067        expressions = f" ({expressions})" if expressions else ""
4068        options = self.expressions(expression, key="options", sep=" ")
4069        options = f" {options}" if options else ""
4070        return f"{kind}{this}{index_type}{expressions}{options}"
def nvl2_sql(self, expression: sqlglot.expressions.Nvl2) -> str:
4072    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4073        if self.NVL2_SUPPORTED:
4074            return self.function_fallback_sql(expression)
4075
4076        case = exp.Case().when(
4077            expression.this.is_(exp.null()).not_(copy=False),
4078            expression.args["true"],
4079            copy=False,
4080        )
4081        else_cond = expression.args.get("false")
4082        if else_cond:
4083            case.else_(else_cond, copy=False)
4084
4085        return self.sql(case)
def comprehension_sql(self, expression: sqlglot.expressions.Comprehension) -> str:
4087    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4088        this = self.sql(expression, "this")
4089        expr = self.sql(expression, "expression")
4090        iterator = self.sql(expression, "iterator")
4091        condition = self.sql(expression, "condition")
4092        condition = f" IF {condition}" if condition else ""
4093        return f"{this} FOR {expr} IN {iterator}{condition}"
def columnprefix_sql(self, expression: sqlglot.expressions.ColumnPrefix) -> str:
4095    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4096        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
def opclass_sql(self, expression: sqlglot.expressions.Opclass) -> str:
4098    def opclass_sql(self, expression: exp.Opclass) -> str:
4099        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def predict_sql(self, expression: sqlglot.expressions.Predict) -> str:
4101    def predict_sql(self, expression: exp.Predict) -> str:
4102        model = self.sql(expression, "this")
4103        model = f"MODEL {model}"
4104        table = self.sql(expression, "expression")
4105        table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4106        parameters = self.sql(expression, "params_struct")
4107        return self.func("PREDICT", model, table, parameters or None)
def forin_sql(self, expression: sqlglot.expressions.ForIn) -> str:
4109    def forin_sql(self, expression: exp.ForIn) -> str:
4110        this = self.sql(expression, "this")
4111        expression_sql = self.sql(expression, "expression")
4112        return f"FOR {this} DO {expression_sql}"
def refresh_sql(self, expression: sqlglot.expressions.Refresh) -> str:
4114    def refresh_sql(self, expression: exp.Refresh) -> str:
4115        this = self.sql(expression, "this")
4116        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4117        return f"REFRESH {table}{this}"
def toarray_sql(self, expression: sqlglot.expressions.ToArray) -> str:
4119    def toarray_sql(self, expression: exp.ToArray) -> str:
4120        arg = expression.this
4121        if not arg.type:
4122            from sqlglot.optimizer.annotate_types import annotate_types
4123
4124            arg = annotate_types(arg, dialect=self.dialect)
4125
4126        if arg.is_type(exp.DataType.Type.ARRAY):
4127            return self.sql(arg)
4128
4129        cond_for_null = arg.is_(exp.null())
4130        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
def tsordstotime_sql(self, expression: sqlglot.expressions.TsOrDsToTime) -> str:
4132    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4133        this = expression.this
4134        time_format = self.format_time(expression)
4135
4136        if time_format:
4137            return self.sql(
4138                exp.cast(
4139                    exp.StrToTime(this=this, format=expression.args["format"]),
4140                    exp.DataType.Type.TIME,
4141                )
4142            )
4143
4144        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4145            return self.sql(this)
4146
4147        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
def tsordstotimestamp_sql(self, expression: sqlglot.expressions.TsOrDsToTimestamp) -> str:
4149    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4150        this = expression.this
4151        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4152            return self.sql(this)
4153
4154        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
def tsordstodatetime_sql(self, expression: sqlglot.expressions.TsOrDsToDatetime) -> str:
4156    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4157        this = expression.this
4158        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4159            return self.sql(this)
4160
4161        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
def tsordstodate_sql(self, expression: sqlglot.expressions.TsOrDsToDate) -> str:
4163    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4164        this = expression.this
4165        time_format = self.format_time(expression)
4166
4167        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4168            return self.sql(
4169                exp.cast(
4170                    exp.StrToTime(this=this, format=expression.args["format"]),
4171                    exp.DataType.Type.DATE,
4172                )
4173            )
4174
4175        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4176            return self.sql(this)
4177
4178        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
def unixdate_sql(self, expression: sqlglot.expressions.UnixDate) -> str:
4180    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4181        return self.sql(
4182            exp.func(
4183                "DATEDIFF",
4184                expression.this,
4185                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4186                "day",
4187            )
4188        )
def lastday_sql(self, expression: sqlglot.expressions.LastDay) -> str:
4190    def lastday_sql(self, expression: exp.LastDay) -> str:
4191        if self.LAST_DAY_SUPPORTS_DATE_PART:
4192            return self.function_fallback_sql(expression)
4193
4194        unit = expression.text("unit")
4195        if unit and unit != "MONTH":
4196            self.unsupported("Date parts are not supported in LAST_DAY.")
4197
4198        return self.func("LAST_DAY", expression.this)
def dateadd_sql(self, expression: sqlglot.expressions.DateAdd) -> str:
4200    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4201        from sqlglot.dialects.dialect import unit_to_str
4202
4203        return self.func(
4204            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4205        )
def arrayany_sql(self, expression: sqlglot.expressions.ArrayAny) -> str:
4207    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4208        if self.CAN_IMPLEMENT_ARRAY_ANY:
4209            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4210            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4211            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4212            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4213
4214        from sqlglot.dialects import Dialect
4215
4216        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4217        if self.dialect.__class__ != Dialect:
4218            self.unsupported("ARRAY_ANY is unsupported")
4219
4220        return self.function_fallback_sql(expression)
def struct_sql(self, expression: sqlglot.expressions.Struct) -> str:
4222    def struct_sql(self, expression: exp.Struct) -> str:
4223        expression.set(
4224            "expressions",
4225            [
4226                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4227                if isinstance(e, exp.PropertyEQ)
4228                else e
4229                for e in expression.expressions
4230            ],
4231        )
4232
4233        return self.function_fallback_sql(expression)
def partitionrange_sql(self, expression: sqlglot.expressions.PartitionRange) -> str:
4235    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4236        low = self.sql(expression, "this")
4237        high = self.sql(expression, "expression")
4238
4239        return f"{low} TO {high}"
def truncatetable_sql(self, expression: sqlglot.expressions.TruncateTable) -> str:
4241    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4242        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4243        tables = f" {self.expressions(expression)}"
4244
4245        exists = " IF EXISTS" if expression.args.get("exists") else ""
4246
4247        on_cluster = self.sql(expression, "cluster")
4248        on_cluster = f" {on_cluster}" if on_cluster else ""
4249
4250        identity = self.sql(expression, "identity")
4251        identity = f" {identity} IDENTITY" if identity else ""
4252
4253        option = self.sql(expression, "option")
4254        option = f" {option}" if option else ""
4255
4256        partition = self.sql(expression, "partition")
4257        partition = f" {partition}" if partition else ""
4258
4259        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
def convert_sql(self, expression: sqlglot.expressions.Convert) -> str:
4263    def convert_sql(self, expression: exp.Convert) -> str:
4264        to = expression.this
4265        value = expression.expression
4266        style = expression.args.get("style")
4267        safe = expression.args.get("safe")
4268        strict = expression.args.get("strict")
4269
4270        if not to or not value:
4271            return ""
4272
4273        # Retrieve length of datatype and override to default if not specified
4274        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4275            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4276
4277        transformed: t.Optional[exp.Expression] = None
4278        cast = exp.Cast if strict else exp.TryCast
4279
4280        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4281        if isinstance(style, exp.Literal) and style.is_int:
4282            from sqlglot.dialects.tsql import TSQL
4283
4284            style_value = style.name
4285            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4286            if not converted_style:
4287                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4288
4289            fmt = exp.Literal.string(converted_style)
4290
4291            if to.this == exp.DataType.Type.DATE:
4292                transformed = exp.StrToDate(this=value, format=fmt)
4293            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4294                transformed = exp.StrToTime(this=value, format=fmt)
4295            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4296                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4297            elif to.this == exp.DataType.Type.TEXT:
4298                transformed = exp.TimeToStr(this=value, format=fmt)
4299
4300        if not transformed:
4301            transformed = cast(this=value, to=to, safe=safe)
4302
4303        return self.sql(transformed)
def copyparameter_sql(self, expression: sqlglot.expressions.CopyParameter) -> str:
4371    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4372        option = self.sql(expression, "this")
4373
4374        if expression.expressions:
4375            upper = option.upper()
4376
4377            # Snowflake FILE_FORMAT options are separated by whitespace
4378            sep = " " if upper == "FILE_FORMAT" else ", "
4379
4380            # Databricks copy/format options do not set their list of values with EQ
4381            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4382            values = self.expressions(expression, flat=True, sep=sep)
4383            return f"{option}{op}({values})"
4384
4385        value = self.sql(expression, "expression")
4386
4387        if not value:
4388            return option
4389
4390        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4391
4392        return f"{option}{op}{value}"
def credentials_sql(self, expression: sqlglot.expressions.Credentials) -> str:
4394    def credentials_sql(self, expression: exp.Credentials) -> str:
4395        cred_expr = expression.args.get("credentials")
4396        if isinstance(cred_expr, exp.Literal):
4397            # Redshift case: CREDENTIALS <string>
4398            credentials = self.sql(expression, "credentials")
4399            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4400        else:
4401            # Snowflake case: CREDENTIALS = (...)
4402            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4403            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4404
4405        storage = self.sql(expression, "storage")
4406        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4407
4408        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4409        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4410
4411        iam_role = self.sql(expression, "iam_role")
4412        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4413
4414        region = self.sql(expression, "region")
4415        region = f" REGION {region}" if region else ""
4416
4417        return f"{credentials}{storage}{encryption}{iam_role}{region}"
def copy_sql(self, expression: sqlglot.expressions.Copy) -> str:
4419    def copy_sql(self, expression: exp.Copy) -> str:
4420        this = self.sql(expression, "this")
4421        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4422
4423        credentials = self.sql(expression, "credentials")
4424        credentials = self.seg(credentials) if credentials else ""
4425        kind = self.seg("FROM" if expression.args.get("kind") else "TO")
4426        files = self.expressions(expression, key="files", flat=True)
4427
4428        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4429        params = self.expressions(
4430            expression,
4431            key="params",
4432            sep=sep,
4433            new_line=True,
4434            skip_last=True,
4435            skip_first=True,
4436            indent=self.COPY_PARAMS_ARE_WRAPPED,
4437        )
4438
4439        if params:
4440            if self.COPY_PARAMS_ARE_WRAPPED:
4441                params = f" WITH ({params})"
4442            elif not self.pretty:
4443                params = f" {params}"
4444
4445        return f"COPY{this}{kind} {files}{credentials}{params}"
def semicolon_sql(self, expression: sqlglot.expressions.Semicolon) -> str:
4447    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4448        return ""
def datadeletionproperty_sql(self, expression: sqlglot.expressions.DataDeletionProperty) -> str:
4450    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4451        on_sql = "ON" if expression.args.get("on") else "OFF"
4452        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4453        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4454        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4455        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4456
4457        if filter_col or retention_period:
4458            on_sql = self.func("ON", filter_col, retention_period)
4459
4460        return f"DATA_DELETION={on_sql}"
def maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4462    def maskingpolicycolumnconstraint_sql(
4463        self, expression: exp.MaskingPolicyColumnConstraint
4464    ) -> str:
4465        this = self.sql(expression, "this")
4466        expressions = self.expressions(expression, flat=True)
4467        expressions = f" USING ({expressions})" if expressions else ""
4468        return f"MASKING POLICY {this}{expressions}"
def gapfill_sql(self, expression: sqlglot.expressions.GapFill) -> str:
4470    def gapfill_sql(self, expression: exp.GapFill) -> str:
4471        this = self.sql(expression, "this")
4472        this = f"TABLE {this}"
4473        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
def scope_resolution(self, rhs: str, scope_name: str) -> str:
4475    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4476        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
def scoperesolution_sql(self, expression: sqlglot.expressions.ScopeResolution) -> str:
4478    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4479        this = self.sql(expression, "this")
4480        expr = expression.expression
4481
4482        if isinstance(expr, exp.Func):
4483            # T-SQL's CLR functions are case sensitive
4484            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4485        else:
4486            expr = self.sql(expression, "expression")
4487
4488        return self.scope_resolution(expr, this)
def parsejson_sql(self, expression: sqlglot.expressions.ParseJSON) -> str:
4490    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4491        if self.PARSE_JSON_NAME is None:
4492            return self.sql(expression.this)
4493
4494        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
def rand_sql(self, expression: sqlglot.expressions.Rand) -> str:
4496    def rand_sql(self, expression: exp.Rand) -> str:
4497        lower = self.sql(expression, "lower")
4498        upper = self.sql(expression, "upper")
4499
4500        if lower and upper:
4501            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4502        return self.func("RAND", expression.this)
def changes_sql(self, expression: sqlglot.expressions.Changes) -> str:
4504    def changes_sql(self, expression: exp.Changes) -> str:
4505        information = self.sql(expression, "information")
4506        information = f"INFORMATION => {information}"
4507        at_before = self.sql(expression, "at_before")
4508        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4509        end = self.sql(expression, "end")
4510        end = f"{self.seg('')}{end}" if end else ""
4511
4512        return f"CHANGES ({information}){at_before}{end}"
def pad_sql(self, expression: sqlglot.expressions.Pad) -> str:
4514    def pad_sql(self, expression: exp.Pad) -> str:
4515        prefix = "L" if expression.args.get("is_left") else "R"
4516
4517        fill_pattern = self.sql(expression, "fill_pattern") or None
4518        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4519            fill_pattern = "' '"
4520
4521        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def summarize_sql(self, expression: sqlglot.expressions.Summarize) -> str:
4523    def summarize_sql(self, expression: exp.Summarize) -> str:
4524        table = " TABLE" if expression.args.get("table") else ""
4525        return f"SUMMARIZE{table} {self.sql(expression.this)}"
def explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4527    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4528        generate_series = exp.GenerateSeries(**expression.args)
4529
4530        parent = expression.parent
4531        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4532            parent = parent.parent
4533
4534        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4535            return self.sql(exp.Unnest(expressions=[generate_series]))
4536
4537        if isinstance(parent, exp.Select):
4538            self.unsupported("GenerateSeries projection unnesting is not supported.")
4539
4540        return self.sql(generate_series)
def arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4542    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4543        exprs = expression.expressions
4544        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4545            rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4546        else:
4547            rhs = self.expressions(expression)
4548
4549        return self.func(name, expression.this, rhs or None)
def converttimezone_sql(self, expression: sqlglot.expressions.ConvertTimezone) -> str:
4551    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4552        if self.SUPPORTS_CONVERT_TIMEZONE:
4553            return self.function_fallback_sql(expression)
4554
4555        source_tz = expression.args.get("source_tz")
4556        target_tz = expression.args.get("target_tz")
4557        timestamp = expression.args.get("timestamp")
4558
4559        if source_tz and timestamp:
4560            timestamp = exp.AtTimeZone(
4561                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4562            )
4563
4564        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4565
4566        return self.sql(expr)
def json_sql(self, expression: sqlglot.expressions.JSON) -> str:
4568    def json_sql(self, expression: exp.JSON) -> str:
4569        this = self.sql(expression, "this")
4570        this = f" {this}" if this else ""
4571
4572        _with = expression.args.get("with")
4573
4574        if _with is None:
4575            with_sql = ""
4576        elif not _with:
4577            with_sql = " WITHOUT"
4578        else:
4579            with_sql = " WITH"
4580
4581        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4582
4583        return f"JSON{this}{with_sql}{unique_sql}"
def jsonvalue_sql(self, expression: sqlglot.expressions.JSONValue) -> str:
4585    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4586        def _generate_on_options(arg: t.Any) -> str:
4587            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4588
4589        path = self.sql(expression, "path")
4590        returning = self.sql(expression, "returning")
4591        returning = f" RETURNING {returning}" if returning else ""
4592
4593        on_condition = self.sql(expression, "on_condition")
4594        on_condition = f" {on_condition}" if on_condition else ""
4595
4596        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
def conditionalinsert_sql(self, expression: sqlglot.expressions.ConditionalInsert) -> str:
4598    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4599        else_ = "ELSE " if expression.args.get("else_") else ""
4600        condition = self.sql(expression, "expression")
4601        condition = f"WHEN {condition} THEN " if condition else else_
4602        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4603        return f"{condition}{insert}"
def multitableinserts_sql(self, expression: sqlglot.expressions.MultitableInserts) -> str:
4605    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4606        kind = self.sql(expression, "kind")
4607        expressions = self.seg(self.expressions(expression, sep=" "))
4608        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4609        return res
def oncondition_sql(self, expression: sqlglot.expressions.OnCondition) -> str:
4611    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4612        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4613        empty = expression.args.get("empty")
4614        empty = (
4615            f"DEFAULT {empty} ON EMPTY"
4616            if isinstance(empty, exp.Expression)
4617            else self.sql(expression, "empty")
4618        )
4619
4620        error = expression.args.get("error")
4621        error = (
4622            f"DEFAULT {error} ON ERROR"
4623            if isinstance(error, exp.Expression)
4624            else self.sql(expression, "error")
4625        )
4626
4627        if error and empty:
4628            error = (
4629                f"{empty} {error}"
4630                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4631                else f"{error} {empty}"
4632            )
4633            empty = ""
4634
4635        null = self.sql(expression, "null")
4636
4637        return f"{empty}{error}{null}"
def jsonextractquote_sql(self, expression: sqlglot.expressions.JSONExtractQuote) -> str:
4639    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4640        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4641        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
def jsonexists_sql(self, expression: sqlglot.expressions.JSONExists) -> str:
4643    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4644        this = self.sql(expression, "this")
4645        path = self.sql(expression, "path")
4646
4647        passing = self.expressions(expression, "passing")
4648        passing = f" PASSING {passing}" if passing else ""
4649
4650        on_condition = self.sql(expression, "on_condition")
4651        on_condition = f" {on_condition}" if on_condition else ""
4652
4653        path = f"{path}{passing}{on_condition}"
4654
4655        return self.func("JSON_EXISTS", this, path)
def arrayagg_sql(self, expression: sqlglot.expressions.ArrayAgg) -> str:
4657    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4658        array_agg = self.function_fallback_sql(expression)
4659
4660        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4661        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4662        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4663            parent = expression.parent
4664            if isinstance(parent, exp.Filter):
4665                parent_cond = parent.expression.this
4666                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4667            else:
4668                this = expression.this
4669                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4670                if this.find(exp.Column):
4671                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4672                    this_sql = (
4673                        self.expressions(this)
4674                        if isinstance(this, exp.Distinct)
4675                        else self.sql(expression, "this")
4676                    )
4677
4678                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4679
4680        return array_agg
def apply_sql(self, expression: sqlglot.expressions.Apply) -> str:
4682    def apply_sql(self, expression: exp.Apply) -> str:
4683        this = self.sql(expression, "this")
4684        expr = self.sql(expression, "expression")
4685
4686        return f"{this} APPLY({expr})"
def grant_sql(self, expression: sqlglot.expressions.Grant) -> str:
4688    def grant_sql(self, expression: exp.Grant) -> str:
4689        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4690
4691        kind = self.sql(expression, "kind")
4692        kind = f" {kind}" if kind else ""
4693
4694        securable = self.sql(expression, "securable")
4695        securable = f" {securable}" if securable else ""
4696
4697        principals = self.expressions(expression, key="principals", flat=True)
4698
4699        grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4700
4701        return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
def grantprivilege_sql(self, expression: sqlglot.expressions.GrantPrivilege):
4703    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4704        this = self.sql(expression, "this")
4705        columns = self.expressions(expression, flat=True)
4706        columns = f"({columns})" if columns else ""
4707
4708        return f"{this}{columns}"
def grantprincipal_sql(self, expression: sqlglot.expressions.GrantPrincipal):
4710    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4711        this = self.sql(expression, "this")
4712
4713        kind = self.sql(expression, "kind")
4714        kind = f"{kind} " if kind else ""
4715
4716        return f"{kind}{this}"
def columns_sql(self, expression: sqlglot.expressions.Columns):
4718    def columns_sql(self, expression: exp.Columns):
4719        func = self.function_fallback_sql(expression)
4720        if expression.args.get("unpack"):
4721            func = f"*{func}"
4722
4723        return func
def overlay_sql(self, expression: sqlglot.expressions.Overlay):
4725    def overlay_sql(self, expression: exp.Overlay):
4726        this = self.sql(expression, "this")
4727        expr = self.sql(expression, "expression")
4728        from_sql = self.sql(expression, "from")
4729        for_sql = self.sql(expression, "for")
4730        for_sql = f" FOR {for_sql}" if for_sql else ""
4731
4732        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4734    @unsupported_args("format")
4735    def todouble_sql(self, expression: exp.ToDouble) -> str:
4736        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
def string_sql(self, expression: sqlglot.expressions.String) -> str:
4738    def string_sql(self, expression: exp.String) -> str:
4739        this = expression.this
4740        zone = expression.args.get("zone")
4741
4742        if zone:
4743            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4744            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4745            # set for source_tz to transpile the time conversion before the STRING cast
4746            this = exp.ConvertTimezone(
4747                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4748            )
4749
4750        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def median_sql(self, expression: sqlglot.expressions.Median):
4752    def median_sql(self, expression: exp.Median):
4753        if not self.SUPPORTS_MEDIAN:
4754            return self.sql(
4755                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4756            )
4757
4758        return self.function_fallback_sql(expression)
def overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4760    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4761        filler = self.sql(expression, "this")
4762        filler = f" {filler}" if filler else ""
4763        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4764        return f"TRUNCATE{filler} {with_count}"
def unixseconds_sql(self, expression: sqlglot.expressions.UnixSeconds) -> str:
4766    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
4767        if self.SUPPORTS_UNIX_SECONDS:
4768            return self.function_fallback_sql(expression)
4769
4770        start_ts = exp.cast(
4771            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
4772        )
4773
4774        return self.sql(
4775            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
4776        )
def arraysize_sql(self, expression: sqlglot.expressions.ArraySize) -> str:
4778    def arraysize_sql(self, expression: exp.ArraySize) -> str:
4779        dim = expression.expression
4780
4781        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
4782        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
4783            if not (dim.is_int and dim.name == "1"):
4784                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
4785            dim = None
4786
4787        # If dimension is required but not specified, default initialize it
4788        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
4789            dim = exp.Literal.number(1)
4790
4791        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
def attach_sql(self, expression: sqlglot.expressions.Attach) -> str:
4793    def attach_sql(self, expression: exp.Attach) -> str:
4794        this = self.sql(expression, "this")
4795        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
4796        expressions = self.expressions(expression)
4797        expressions = f" ({expressions})" if expressions else ""
4798
4799        return f"ATTACH{exists_sql} {this}{expressions}"
def detach_sql(self, expression: sqlglot.expressions.Detach) -> str:
4801    def detach_sql(self, expression: exp.Detach) -> str:
4802        this = self.sql(expression, "this")
4803        # the DATABASE keyword is required if IF EXISTS is set
4804        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
4805        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
4806        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
4807
4808        return f"DETACH{exists_sql} {this}"
def attachoption_sql(self, expression: sqlglot.expressions.AttachOption) -> str:
4810    def attachoption_sql(self, expression: exp.AttachOption) -> str:
4811        this = self.sql(expression, "this")
4812        value = self.sql(expression, "expression")
4813        value = f" {value}" if value else ""
4814        return f"{this}{value}"
def featuresattime_sql(self, expression: sqlglot.expressions.FeaturesAtTime) -> str:
4816    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4817        this_sql = self.sql(expression, "this")
4818        if isinstance(expression.this, exp.Table):
4819            this_sql = f"TABLE {this_sql}"
4820
4821        return self.func(
4822            "FEATURES_AT_TIME",
4823            this_sql,
4824            expression.args.get("time"),
4825            expression.args.get("num_rows"),
4826            expression.args.get("ignore_feature_nulls"),
4827        )
def watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4829    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4830        return (
4831            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
4832        )
def encodeproperty_sql(self, expression: sqlglot.expressions.EncodeProperty) -> str:
4834    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
4835        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
4836        encode = f"{encode} {self.sql(expression, 'this')}"
4837
4838        properties = expression.args.get("properties")
4839        if properties:
4840            encode = f"{encode} {self.properties(properties)}"
4841
4842        return encode
def includeproperty_sql(self, expression: sqlglot.expressions.IncludeProperty) -> str:
4844    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
4845        this = self.sql(expression, "this")
4846        include = f"INCLUDE {this}"
4847
4848        column_def = self.sql(expression, "column_def")
4849        if column_def:
4850            include = f"{include} {column_def}"
4851
4852        alias = self.sql(expression, "alias")
4853        if alias:
4854            include = f"{include} AS {alias}"
4855
4856        return include
def xmlelement_sql(self, expression: sqlglot.expressions.XMLElement) -> str:
4858    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
4859        name = f"NAME {self.sql(expression, 'this')}"
4860        return self.func("XMLELEMENT", name, *expression.expressions)
def xmlkeyvalueoption_sql(self, expression: sqlglot.expressions.XMLKeyValueOption) -> str:
4862    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
4863        this = self.sql(expression, "this")
4864        expr = self.sql(expression, "expression")
4865        expr = f"({expr})" if expr else ""
4866        return f"{this}{expr}"
def partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4868    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
4869        partitions = self.expressions(expression, "partition_expressions")
4870        create = self.expressions(expression, "create_expressions")
4871        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4873    def partitionbyrangepropertydynamic_sql(
4874        self, expression: exp.PartitionByRangePropertyDynamic
4875    ) -> str:
4876        start = self.sql(expression, "start")
4877        end = self.sql(expression, "end")
4878
4879        every = expression.args["every"]
4880        if isinstance(every, exp.Interval) and every.this.is_string:
4881            every.this.replace(exp.Literal.number(every.name))
4882
4883        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
def unpivotcolumns_sql(self, expression: sqlglot.expressions.UnpivotColumns) -> str:
4885    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
4886        name = self.sql(expression, "this")
4887        values = self.expressions(expression, flat=True)
4888
4889        return f"NAME {name} VALUE {values}"
def analyzesample_sql(self, expression: sqlglot.expressions.AnalyzeSample) -> str:
4891    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
4892        kind = self.sql(expression, "kind")
4893        sample = self.sql(expression, "sample")
4894        return f"SAMPLE {sample} {kind}"
def analyzestatistics_sql(self, expression: sqlglot.expressions.AnalyzeStatistics) -> str:
4896    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
4897        kind = self.sql(expression, "kind")
4898        option = self.sql(expression, "option")
4899        option = f" {option}" if option else ""
4900        this = self.sql(expression, "this")
4901        this = f" {this}" if this else ""
4902        columns = self.expressions(expression)
4903        columns = f" {columns}" if columns else ""
4904        return f"{kind}{option} STATISTICS{this}{columns}"
def analyzehistogram_sql(self, expression: sqlglot.expressions.AnalyzeHistogram) -> str:
4906    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
4907        this = self.sql(expression, "this")
4908        columns = self.expressions(expression)
4909        inner_expression = self.sql(expression, "expression")
4910        inner_expression = f" {inner_expression}" if inner_expression else ""
4911        update_options = self.sql(expression, "update_options")
4912        update_options = f" {update_options} UPDATE" if update_options else ""
4913        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def analyzedelete_sql(self, expression: sqlglot.expressions.AnalyzeDelete) -> str:
4915    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
4916        kind = self.sql(expression, "kind")
4917        kind = f" {kind}" if kind else ""
4918        return f"DELETE{kind} STATISTICS"
def analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4920    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
4921        inner_expression = self.sql(expression, "expression")
4922        return f"LIST CHAINED ROWS{inner_expression}"
def analyzevalidate_sql(self, expression: sqlglot.expressions.AnalyzeValidate) -> str:
4924    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
4925        kind = self.sql(expression, "kind")
4926        this = self.sql(expression, "this")
4927        this = f" {this}" if this else ""
4928        inner_expression = self.sql(expression, "expression")
4929        return f"VALIDATE {kind}{this}{inner_expression}"
def analyze_sql(self, expression: sqlglot.expressions.Analyze) -> str:
4931    def analyze_sql(self, expression: exp.Analyze) -> str:
4932        options = self.expressions(expression, key="options", sep=" ")
4933        options = f" {options}" if options else ""
4934        kind = self.sql(expression, "kind")
4935        kind = f" {kind}" if kind else ""
4936        this = self.sql(expression, "this")
4937        this = f" {this}" if this else ""
4938        mode = self.sql(expression, "mode")
4939        mode = f" {mode}" if mode else ""
4940        properties = self.sql(expression, "properties")
4941        properties = f" {properties}" if properties else ""
4942        partition = self.sql(expression, "partition")
4943        partition = f" {partition}" if partition else ""
4944        inner_expression = self.sql(expression, "expression")
4945        inner_expression = f" {inner_expression}" if inner_expression else ""
4946        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
def xmltable_sql(self, expression: sqlglot.expressions.XMLTable) -> str:
4948    def xmltable_sql(self, expression: exp.XMLTable) -> str:
4949        this = self.sql(expression, "this")
4950        namespaces = self.expressions(expression, key="namespaces")
4951        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
4952        passing = self.expressions(expression, key="passing")
4953        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
4954        columns = self.expressions(expression, key="columns")
4955        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
4956        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4957        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
def xmlnamespace_sql(self, expression: sqlglot.expressions.XMLNamespace) -> str:
4959    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4960        this = self.sql(expression, "this")
4961        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
def export_sql(self, expression: sqlglot.expressions.Export) -> str:
4963    def export_sql(self, expression: exp.Export) -> str:
4964        this = self.sql(expression, "this")
4965        connection = self.sql(expression, "connection")
4966        connection = f"WITH CONNECTION {connection} " if connection else ""
4967        options = self.sql(expression, "options")
4968        return f"EXPORT DATA {connection}{options} AS {this}"
def declare_sql(self, expression: sqlglot.expressions.Declare) -> str:
4970    def declare_sql(self, expression: exp.Declare) -> str:
4971        return f"DECLARE {self.expressions(expression, flat=True)}"
def declareitem_sql(self, expression: sqlglot.expressions.DeclareItem) -> str:
4973    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
4974        variable = self.sql(expression, "this")
4975        default = self.sql(expression, "default")
4976        default = f" = {default}" if default else ""
4977
4978        kind = self.sql(expression, "kind")
4979        if isinstance(expression.args.get("kind"), exp.Schema):
4980            kind = f"TABLE {kind}"
4981
4982        return f"{variable} AS {kind}{default}"
def recursivewithsearch_sql(self, expression: sqlglot.expressions.RecursiveWithSearch) -> str:
4984    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
4985        kind = self.sql(expression, "kind")
4986        this = self.sql(expression, "this")
4987        set = self.sql(expression, "expression")
4988        using = self.sql(expression, "using")
4989        using = f" USING {using}" if using else ""
4990
4991        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
4992
4993        return f"{kind_sql} {this} SET {set}{using}"
def parameterizedagg_sql(self, expression: sqlglot.expressions.ParameterizedAgg) -> str:
4995    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
4996        params = self.expressions(expression, key="params", flat=True)
4997        return self.func(expression.name, *expression.expressions) + f"({params})"
def anonymousaggfunc_sql(self, expression: sqlglot.expressions.AnonymousAggFunc) -> str:
4999    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5000        return self.func(expression.name, *expression.expressions)
def combinedaggfunc_sql(self, expression: sqlglot.expressions.CombinedAggFunc) -> str:
5002    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5003        return self.anonymousaggfunc_sql(expression)
def combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5005    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5006        return self.parameterizedagg_sql(expression)
def show_sql(self, expression: sqlglot.expressions.Show) -> str:
5008    def show_sql(self, expression: exp.Show) -> str:
5009        self.unsupported("Unsupported SHOW statement")
5010        return ""
def get_put_sql( self, expression: sqlglot.expressions.Put | sqlglot.expressions.Get) -> str:
5012    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5013        # Snowflake GET/PUT statements:
5014        #   PUT <file> <internalStage> <properties>
5015        #   GET <internalStage> <file> <properties>
5016        props = expression.args.get("properties")
5017        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5018        this = self.sql(expression, "this")
5019        target = self.sql(expression, "target")
5020
5021        if isinstance(expression, exp.Put):
5022            return f"PUT {this} {target}{props_sql}"
5023        else:
5024            return f"GET {target} {this}{props_sql}"
def translatecharacters_sql(self, expression: sqlglot.expressions.TranslateCharacters):
5026    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5027        this = self.sql(expression, "this")
5028        expr = self.sql(expression, "expression")
5029        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5030        return f"TRANSLATE({this} USING {expr}{with_error})"
def decodecase_sql(self, expression: sqlglot.expressions.DecodeCase) -> str:
5032    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5033        if self.SUPPORTS_DECODE_CASE:
5034            return self.func("DECODE", *expression.expressions)
5035
5036        expression, *expressions = expression.expressions
5037
5038        ifs = []
5039        for search, result in zip(expressions[::2], expressions[1::2]):
5040            if isinstance(search, exp.Literal):
5041                ifs.append(exp.If(this=expression.eq(search), true=result))
5042            elif isinstance(search, exp.Null):
5043                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5044            else:
5045                if isinstance(search, exp.Binary):
5046                    search = exp.paren(search)
5047
5048                cond = exp.or_(
5049                    expression.eq(search),
5050                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5051                    copy=False,
5052                )
5053                ifs.append(exp.If(this=cond, true=result))
5054
5055        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5056        return self.sql(case)
def semanticview_sql(self, expression: sqlglot.expressions.SemanticView) -> str:
5058    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5059        this = self.sql(expression, "this")
5060        this = self.seg(this, sep="")
5061        dimensions = self.expressions(
5062            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5063        )
5064        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5065        metrics = self.expressions(
5066            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5067        )
5068        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5069        where = self.sql(expression, "where")
5070        where = self.seg(f"WHERE {where}") if where else ""
5071        return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"